/**
 * Image.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

define("moxie/image/Image", [
	"moxie/core/utils/Basic",
	"moxie/core/utils/Dom",
	"moxie/core/Exceptions",
	"moxie/file/FileReaderSync",
	"moxie/xhr/XMLHttpRequest",
	"moxie/runtime/Runtime",
	"moxie/runtime/RuntimeClient",
	"moxie/runtime/Transporter",
	"moxie/core/utils/Env",
	"moxie/core/EventTarget",
	"moxie/file/Blob",
	"moxie/file/File",
	"moxie/core/utils/Encode"
], function(Basic, Dom, x, FileReaderSync, XMLHttpRequest, Runtime, RuntimeClient, Transporter, Env, EventTarget, Blob, File, Encode) {
	/**
	Image preloading and manipulation utility. Additionally it provides access to image meta info (Exif, GPS) and raw binary data.

	@class moxie/image/Image
	@constructor
	@extends EventTarget
	*/
	var dispatches = [
		'progress',

		/**
		Dispatched when loading is complete.

		@event load
		@param {Object} event
		*/
		'load',

		'error',

		/**
		Dispatched when resize operation is complete.

		@event resize
		@param {Object} event
		*/
		'resize',

		/**
		Dispatched when visual representation of the image is successfully embedded
		into the corresponsing container.

		@event embedded
		@param {Object} event
		*/
		'embedded'
	];

	function Image() {

		RuntimeClient.call(this);

		Basic.extend(this, {
			/**
			Unique id of the component

			@property uid
			@type {String}
			*/
			uid: Basic.guid('uid_'),

			/**
			Unique id of the connected runtime, if any.

			@property ruid
			@type {String}
			*/
			ruid: null,

			/**
			Name of the file, that was used to create an image, if available. If not equals to empty string.

			@property name
			@type {String}
			@default ""
			*/
			name: "",

			/**
			Size of the image in bytes. Actual value is set only after image is preloaded.

			@property size
			@type {Number}
			@default 0
			*/
			size: 0,

			/**
			Width of the image. Actual value is set only after image is preloaded.

			@property width
			@type {Number}
			@default 0
			*/
			width: 0,

			/**
			Height of the image. Actual value is set only after image is preloaded.

			@property height
			@type {Number}
			@default 0
			*/
			height: 0,

			/**
			Mime type of the image. Currently only image/jpeg and image/png are supported. Actual value is set only after image is preloaded.

			@property type
			@type {String}
			@default ""
			*/
			type: "",

			/**
			Holds meta info (Exif, GPS). Is populated only for image/jpeg. Actual value is set only after image is preloaded.

			@property meta
			@type {Object}
			@default {}
			*/
			meta: {},

			/**
			Alias for load method, that takes another moxie.image.Image object as a source (see load).

			@method clone
			@param {Image} src Source for the image
			@param {Boolean} [exact=false] Whether to activate in-depth clone mode
			*/
			clone: function() {
				this.load.apply(this, arguments);
			},

			/**
			Loads image from various sources. Currently the source for new image can be: moxie.image.Image,
			moxie.file.Blob/moxie.file.File, native Blob/File, dataUrl or URL. Depending on the type of the
			source, arguments - differ. When source is URL, Image will be downloaded from remote destination
			and loaded in memory.

			@example
				var img = new moxie.image.Image();
				img.onload = function() {
					var blob = img.getAsBlob();

					var formData = new moxie.xhr.FormData();
					formData.append('file', blob);

					var xhr = new moxie.xhr.XMLHttpRequest();
					xhr.onload = function() {
						// upload complete
					};
					xhr.open('post', 'upload.php');
					xhr.send(formData);
				};
				img.load("http://www.moxiecode.com/images/mox-logo.jpg"); // notice file extension (.jpg)

			@method load
			@param {Image|Blob|File|String} src Source for the image
			@param {Boolean|Object} [mixed]
			*/
			load: function() {
				_load.apply(this, arguments);
			},

			/**
			Resizes the image to fit the specified width/height. If crop is specified, image will also be
			cropped to the exact dimensions.

			@method resize
			@since 3.0
			@param {Object} options
				@param {Number} options.width Resulting width
				@param {Number} [options.height=width] Resulting height (optional, if not supplied will default to width)
				@param {String} [options.type='image/jpeg'] MIME type of the resulting image
				@param {Number} [options.quality=90] In the case of JPEG, controls the quality of resulting image
				@param {Boolean} [options.crop='cc'] If not falsy, image will be cropped, by default from center
				@param {Boolean} [options.fit=true] Whether to upscale the image to fit the exact dimensions
				@param {Boolean} [options.preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
				@param {String} [options.resample='default'] Resampling algorithm to use during resize
				@param {Boolean} [options.multipass=true] Whether to scale the image in steps (results in better quality)
			*/
			resize: function(options) {
				var self = this;
				var orientation;
				var scale;

				var srcRect = {
					x: 0,
					y: 0,
					width: self.width,
					height: self.height
				};

				var opts = Basic.extendIf({
					width: self.width,
					height: self.height,
					type: self.type || 'image/jpeg',
					quality: 90,
					crop: false,
					fit: true,
					preserveHeaders: true,
					resample: 'default',
					multipass: true
				}, options);

				try {
					if (!self.size) { // only preloaded image objects can be used as source
						throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
					}

					// no way to reliably intercept the crash due to high resolution, so we simply avoid it
					if (self.width > Image.MAX_RESIZE_WIDTH || self.height > Image.MAX_RESIZE_HEIGHT) {
						throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR);
					}

					// take into account orientation tag
					orientation = (self.meta && self.meta.tiff && self.meta.tiff.Orientation) || 1;

					if (Basic.inArray(orientation, [5,6,7,8]) !== -1) { // values that require 90 degree rotation
						var tmp = opts.width;
						opts.width = opts.height;
						opts.height = tmp;
					}

					if (opts.crop) {
						scale = Math.max(opts.width/self.width, opts.height/self.height);

						if (options.fit) {
							// first scale it up or down to fit the original image
							srcRect.width = Math.min(Math.ceil(opts.width/scale), self.width);
							srcRect.height = Math.min(Math.ceil(opts.height/scale), self.height);

							// recalculate the scale for adapted dimensions
							scale = opts.width/srcRect.width;
						} else {
							srcRect.width = Math.min(opts.width, self.width);
							srcRect.height = Math.min(opts.height, self.height);

							// now we do not need to scale it any further
							scale = 1;
						}

						if (typeof(opts.crop) === 'boolean') {
							opts.crop = 'cc';
						}

						switch (opts.crop.toLowerCase().replace(/_/, '-')) {
							case 'rb':
							case 'right-bottom':
								srcRect.x = self.width - srcRect.width;
								srcRect.y = self.height - srcRect.height;
								break;

							case 'cb':
							case 'center-bottom':
								srcRect.x = Math.floor((self.width - srcRect.width) / 2);
								srcRect.y = self.height - srcRect.height;
								break;

							case 'lb':
							case 'left-bottom':
								srcRect.x = 0;
								srcRect.y = self.height - srcRect.height;
								break;

							case 'lt':
							case 'left-top':
								srcRect.x = 0;
								srcRect.y = 0;
								break;

							case 'ct':
							case 'center-top':
								srcRect.x = Math.floor((self.width - srcRect.width) / 2);
								srcRect.y = 0;
								break;

							case 'rt':
							case 'right-top':
								srcRect.x = self.width - srcRect.width;
								srcRect.y = 0;
								break;

							case 'rc':
							case 'right-center':
							case 'right-middle':
								srcRect.x = self.width - srcRect.width;
								srcRect.y = Math.floor((self.height - srcRect.height) / 2);
								break;

							case 'lc':
							case 'left-center':
							case 'left-middle':
								srcRect.x = 0;
								srcRect.y = Math.floor((self.height - srcRect.height) / 2);
								break;

							case 'cc':
							case 'center-center':
							case 'center-middle':
							default:
								srcRect.x = Math.floor((self.width - srcRect.width) / 2);
								srcRect.y = Math.floor((self.height - srcRect.height) / 2);
						}

						// original image might be smaller than requested crop, so - avoid negative values
						srcRect.x = Math.max(srcRect.x, 0);
						srcRect.y = Math.max(srcRect.y, 0);
					} else {
						scale = Math.min(opts.width/self.width, opts.height/self.height);

						// do not upscale if we were asked to not fit it
						if (scale > 1 && !opts.fit) {
							scale = 1;
						}
					}

					this.exec('Image', 'resize', srcRect, scale, opts);
				} catch(ex) {
					// for now simply trigger error event
					self.trigger('error', ex.code);
				}
			},

			/**
			Downsizes the image to fit the specified width/height. If crop is supplied, image will be cropped to exact dimensions.

			@method downsize
			@deprecated use resize()
			*/
			downsize: function(options) {
				var defaults = {
					width: this.width,
					height: this.height,
					type: this.type || 'image/jpeg',
					quality: 90,
					crop: false,
					fit: false,
					preserveHeaders: true,
					resample: 'default'
				}, opts;

				if (typeof(options) === 'object') {
					opts = Basic.extend(defaults, options);
				} else {
					// for backward compatibility
					opts = Basic.extend(defaults, {
						width: arguments[0],
						height: arguments[1],
						crop: arguments[2],
						preserveHeaders: arguments[3]
					});
				}

				this.resize(opts);
			},

			/**
			Alias for downsize(width, height, true). (see downsize)

			@method crop
			@param {Number} width Resulting width
			@param {Number} [height=width] Resulting height (optional, if not supplied will default to width)
			@param {Boolean} [preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
			*/
			crop: function(width, height, preserveHeaders) {
				this.downsize(width, height, true, preserveHeaders);
			},

			getAsCanvas: function() {
				if (!Env.can('create_canvas')) {
					throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
				}
				return this.exec('Image', 'getAsCanvas');
			},

			/**
			Retrieves image in it's current state as moxie.file.Blob object. Cannot be run on empty or image in progress (throws
			DOMException.INVALID_STATE_ERR).

			@method getAsBlob
			@param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
			@param {Number} [quality=90] Applicable only together with mime type image/jpeg
			@return {Blob} Image as Blob
			*/
			getAsBlob: function(type, quality) {
				if (!this.size) {
					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
				}
				return this.exec('Image', 'getAsBlob', type || 'image/jpeg', quality || 90);
			},

			/**
			Retrieves image in it's current state as dataURL string. Cannot be run on empty or image in progress (throws
			DOMException.INVALID_STATE_ERR).

			@method getAsDataURL
			@param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
			@param {Number} [quality=90] Applicable only together with mime type image/jpeg
			@return {String} Image as dataURL string
			*/
			getAsDataURL: function(type, quality) {
				if (!this.size) {
					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
				}
				return this.exec('Image', 'getAsDataURL', type || 'image/jpeg', quality || 90);
			},

			/**
			Retrieves image in it's current state as binary string. Cannot be run on empty or image in progress (throws
			DOMException.INVALID_STATE_ERR).

			@method getAsBinaryString
			@param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
			@param {Number} [quality=90] Applicable only together with mime type image/jpeg
			@return {String} Image as binary string
			*/
			getAsBinaryString: function(type, quality) {
				var dataUrl = this.getAsDataURL(type, quality);
				return Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7));
			},

			/**
			Embeds a visual representation of the image into the specified node. Depending on the runtime,
			it might be a canvas, an img node or a thrid party shim object (Flash or SilverLight - very rare,
			can be used in legacy browsers that do not have canvas or proper dataURI support).

			@method embed
			@param {DOMElement} el DOM element to insert the image object into
			@param {Object} [options]
				@param {Number} [options.width] The width of an embed (defaults to the image width)
				@param {Number} [options.height] The height of an embed (defaults to the image height)
				@param {String} [options.type="image/jpeg"] Mime type
				@param {Number} [options.quality=90] Quality of an embed, if mime type is image/jpeg
				@param {Boolean} [options.crop=false] Whether to crop an embed to the specified dimensions
				@param {Boolean} [options.fit=true] By default thumbs will be up- or downscaled as necessary to fit the dimensions
			*/
			embed: function(el, options) {
				var self = this
				, runtime // this has to be outside of all the closures to contain proper runtime
				;

				var opts = Basic.extend({
					width: this.width,
					height: this.height,
					type: this.type || 'image/jpeg',
					quality: 90,
					fit: true,
					resample: 'nearest'
				}, options);

				function render(type, quality) {
					var img = this;

					// if possible, embed a canvas element directly
					if (Env.can('create_canvas')) {
						var canvas = img.getAsCanvas();
						if (canvas) {
							el.appendChild(canvas);
							canvas = null;
							img.destroy();
							self.trigger('embedded');
							return;
						}
					}

					var dataUrl = img.getAsDataURL(type, quality);
					if (!dataUrl) {
						throw new x.ImageError(x.ImageError.WRONG_FORMAT);
					}

					if (Env.can('use_data_uri_of', dataUrl.length)) {
						el.innerHTML = '<img src="' + dataUrl + '" width="' + img.width + '" height="' + img.height + '" alt="" />';
						img.destroy();
						self.trigger('embedded');
					} else {
						var tr = new Transporter();

						tr.bind("TransportingComplete", function() {
							runtime = self.connectRuntime(this.result.ruid);

							self.bind("Embedded", function() {
								// position and size properly
								Basic.extend(runtime.getShimContainer().style, {
									//position: 'relative',
									top: '0px',
									left: '0px',
									width: img.width + 'px',
									height: img.height + 'px'
								});

								// some shims (Flash/SilverLight) reinitialize, if parent element is hidden, reordered or it's
								// position type changes (in Gecko), but since we basically need this only in IEs 6/7 and
								// sometimes 8 and they do not have this problem, we can comment this for now
								/*tr.bind("RuntimeInit", function(e, runtime) {
									tr.destroy();
									runtime.destroy();
									onResize.call(self); // re-feed our image data
								});*/

								runtime = null; // release
							}, 999);

							runtime.exec.call(self, "ImageView", "display", this.result.uid, width, height);
							img.destroy();
						});

						tr.transport(Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)), type, {
							required_caps: {
								display_media: true
							},
							runtime_order: 'flash,silverlight',
							container: el
						});
					}
				}

				try {
					if (!(el = Dom.get(el))) {
						throw new x.DOMException(x.DOMException.INVALID_NODE_TYPE_ERR);
					}

					if (!this.size) { // only preloaded image objects can be used as source
						throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
					}

					// high-resolution images cannot be consistently handled across the runtimes
					if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) {
						//throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR);
					}

					var imgCopy = new Image();

					imgCopy.bind("Resize", function() {
						render.call(this, opts.type, opts.quality);
					});

					imgCopy.bind("Load", function() {
						this.downsize(opts);
					});

					// if embedded thumb data is available and dimensions are big enough, use it
					if (this.meta.thumb && this.meta.thumb.width >= opts.width && this.meta.thumb.height >= opts.height) {
						imgCopy.load(this.meta.thumb.data);
					} else {
						imgCopy.clone(this, false);
					}

					return imgCopy;
				} catch(ex) {
					// for now simply trigger error event
					this.trigger('error', ex.code);
				}
			},

			/**
			Properly destroys the image and frees resources in use. If any. Recommended way to dispose
			moxie.image.Image object.

			@method destroy
			*/
			destroy: function() {
				if (this.ruid) {
					this.getRuntime().exec.call(this, 'Image', 'destroy');
					this.disconnectRuntime();
				}
				if (this.meta && this.meta.thumb) {
					// thumb is blob, make sure we destroy it first
					this.meta.thumb.data.destroy();
				}
				this.unbindAll();
			}
		});

		// this is here, because in order to bind properly, we need uid, which is created above
		this.handleEventProps(dispatches);

		this.bind('Load Resize', function() {
			return _updateInfo.call(this); // if operation fails (e.g. image is neither PNG nor JPEG) cancel all pending events
		}, 999);

		function _updateInfo(info) {
			try {
				if (!info) {
					info = this.exec('Image', 'getInfo');
				}

				this.size = info.size;
				this.width = info.width;
				this.height = info.height;
				this.type = info.type;
				this.meta = info.meta;

				// update file name, only if empty
				if (this.name === '') {
					this.name = info.name;
				}

				return true;
			} catch(ex) {
				this.trigger('error', ex.code);
				return false;
			}
		}

		function _load(src) {
			var srcType = Basic.typeOf(src);

			try {
				// if source is Image
				if (src instanceof Image) {
					if (!src.size) { // only preloaded image objects can be used as source
						throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
					}
					_loadFromImage.apply(this, arguments);
				}
				// if source is o.Blob/o.File
				else if (src instanceof Blob) {
					if (!~Basic.inArray(src.type, ['image/jpeg', 'image/png'])) {
						throw new x.ImageError(x.ImageError.WRONG_FORMAT);
					}
					_loadFromBlob.apply(this, arguments);
				}
				// if native blob/file
				else if (Basic.inArray(srcType, ['blob', 'file']) !== -1) {
					_load.call(this, new File(null, src), arguments[1]);
				}
				// if String
				else if (srcType === 'string') {
					// if dataUrl String
					if (src.substr(0, 5) === 'data:') {
						_load.call(this, new Blob(null, { data: src }), arguments[1]);
					}
					// else assume Url, either relative or absolute
					else {
						_loadFromUrl.apply(this, arguments);
					}
				}
				// if source seems to be an img node
				else if (srcType === 'node' && src.nodeName.toLowerCase() === 'img') {
					_load.call(this, src.src, arguments[1]);
				}
				else {
					throw new x.DOMException(x.DOMException.TYPE_MISMATCH_ERR);
				}
			} catch(ex) {
				// for now simply trigger error event
				this.trigger('error', ex.code);
			}
		}

		function _loadFromImage(img, exact) {
			var runtime = this.connectRuntime(img.ruid);
			this.ruid = runtime.uid;
			runtime.exec.call(this, 'Image', 'loadFromImage', img, (Basic.typeOf(exact) === 'undefined' ? true : exact));
		}

		function _loadFromBlob(blob, options) {
			var self = this;

			self.name = blob.name || '';

			function exec(runtime) {
				self.ruid = runtime.uid;
				runtime.exec.call(self, 'Image', 'loadFromBlob', blob);
			}

			if (blob.isDetached()) {
				this.bind('RuntimeInit', function(e, runtime) {
					exec(runtime);
				});

				// convert to object representation
				if (options && typeof(options.required_caps) === 'string') {
					options.required_caps = Runtime.parseCaps(options.required_caps);
				}

				this.connectRuntime(Basic.extend({
					required_caps: {
						access_image_binary: true,
						resize_image: true
					}
				}, options));
			} else {
				exec(this.connectRuntime(blob.ruid));
			}
		}

		function _loadFromUrl(url, options) {
			var self = this, xhr;

			xhr = new XMLHttpRequest();

			xhr.open('get', url);
			xhr.responseType = 'blob';

			xhr.onprogress = function(e) {
				self.trigger(e);
			};

			xhr.onload = function() {
				_loadFromBlob.call(self, xhr.response, true);
			};

			xhr.onerror = function(e) {
				self.trigger(e);
			};

			xhr.onloadend = function() {
				xhr.destroy();
			};

			xhr.bind('RuntimeError', function(e, err) {
				self.trigger('RuntimeError', err);
			});

			xhr.send(null, options);
		}
	}

	// virtual world will crash on you if image has a resolution higher than this:
	Image.MAX_RESIZE_WIDTH = 8192;
	Image.MAX_RESIZE_HEIGHT = 8192;

	Image.prototype = EventTarget.instance;

	return Image;
});


/**
 * Extra.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/**
@class moxie/core/utils/Extra
@private
@static
*/

define("moxie/core/utils/Extra", [
	"moxie/core/utils/Basic",
	"moxie/core/utils/Env"
], function(o, Env) {
	/**
	Defines property with specified descriptor on an object

	@method defineProperty
	@static
	@param {Object} obj Object to add property to
	@param {String} prop Property name
	@param {Object} desc Set of key-value pairs defining descriptor for the property
	*/
	var defineProperty = function(obj, prop, desc)
	{
		if (o.typeOf(desc) === 'object') {
			defineGSetter.call(obj, prop, desc, 'get');

			if (!Object.defineProperty) {
				// additionally call it for setter
				defineGSetter.call(obj, prop, desc, 'set');
			}
		}
	};

	/**
	Defines getter for the property

	@method defineGetter
	@static
	@param {Object} obj Object to add property to
	@param {String} prop Property name
	@param {Object} desc Set of key-value pairs defining descriptor for the property
	*/
	var defineGetter = function(obj, prop, desc) {
		return defineGSetter.call(obj, prop, desc, 'get');
	};

	/**
	Defines setter for the property

	@method defineSetter
	@static
	@param {Object} obj Object to add property to
	@param {String} prop Property name
	@param {Object} desc Set of key-value pairs defining descriptor for the property
	*/
	var defineSetter = function(obj, prop, desc) {
		return defineGSetter.call(obj, prop, desc, 'set');
	};

	/**
	Defines getter or setter, depending on a type param

	@method defineGSetter
	@static
	@private
	@param {String} prop Property name
	@param {Object} desc Set of key-value pairs defining descriptor for the property
	@param {String} type Can take value of 'set' or 'get'
	*/
	var defineGSetter = function(prop, desc, type) {
		var defaults = {
			enumerable: true,
			configurable: true
		}
		, fn
		, camelType
		, self = this
		;

		type = type.toLowerCase();
		camelType = type.replace(/^[gs]/, function($1) { return $1.toUpperCase(); });

		// define function object for fallback
		if (o.typeOf(desc) === 'function') {
			fn = desc;
			desc = {};
			desc[type] = fn;
		} else if (o.typeOf(desc[type]) === 'function') {
			fn = desc[type];
		} else {
			return;
		}

		if (Env.can('define_property')) {
			if (Object.defineProperty) {
				return Object.defineProperty(this, prop, o.extend({}, defaults, desc));
			} else {
				return self['__define' + camelType + 'ter__'](prop, fn);
			}
		}
	};

	return {
		defineGetter: defineGetter,
		defineSetter: defineSetter,
		defineProperty: defineProperty
	};
});


#!/usr/bin/env node
'use strict';

var helpers = require('./helpers');
var chokidar = require('chokidar');
var colors = require('colors');
var argv = require('yargs').argv;

var watchersInitialized = false;
var main = void 0;

/* Secure mode */

var secureMode = false;
if (argv.secure) secureMode = true;

var uninstallMode = true;
if (argv['dont-uninstall']) uninstallMode = false;

/* Watch files and repeat drill
 * Add a watcher, call main wrapper to repeat cycle
 */

var initializeWatchers = function initializeWatchers() {
    var watcher = chokidar.watch('**/*.js', {
        ignored: 'node_modules'
    });
    watcher.on('change', main).on('unlink', main);

    watchersInitialized = true;
    console.log('Watchers initialized');
};

/* Main wrapper
 * Get installed modules from package.json
 * Get used modules from all files
 * Install used modules that are not installed
 * Remove installed modules that are not used
 * After setup, initialize watchers
 */

main = function main() {
    if (!helpers.packageJSONExists()) {
        console.log(colors.red('package.json does not exist'));
        console.log(colors.red('You can create one by using `npm init`'));
        return;
    }

    var installedModules = [];
    installedModules = helpers.getInstalledModules();

    var usedModules = helpers.getUsedModules();
    usedModules = helpers.filterRegistryModules(usedModules);

    // removeUnusedModules

    if (uninstallMode) {
        var unusedModules = helpers.diff(installedModules, usedModules);
        var _iteratorNormalCompletion = true;
        var _didIteratorError = false;
        var _iteratorError = undefined;

        try {
            for (var _iterator = unusedModules[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
                var module = _step.value;
                helpers.uninstallModule(module);
            }
        } catch (err) {
            _didIteratorError = true;
            _iteratorError = err;
        } finally {
            try {
                if (!_iteratorNormalCompletion && _iterator.return) {
                    _iterator.return();
                }
            } finally {
                if (_didIteratorError) {
                    throw _iteratorError;
                }
            }
        }
    }

    // installModules

    var modulesNotInstalled = helpers.diff(usedModules, installedModules);
    var _iteratorNormalCompletion2 = true;
    var _didIteratorError2 = false;
    var _iteratorError2 = undefined;

    try {
        for (var _iterator2 = modulesNotInstalled[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
            var _module = _step2.value;

            if (secureMode) helpers.installModuleIfTrusted(_module);else helpers.installModule(_module);
        }
    } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
    } finally {
        try {
            if (!_iteratorNormalCompletion2 && _iterator2.return) {
                _iterator2.return();
            }
        } finally {
            if (_didIteratorError2) {
                throw _iteratorError2;
            }
        }
    }

    helpers.cleanup();
    if (!watchersInitialized) initializeWatchers();
};

/* Turn the key */
main();


// @flow
'use strict'

/*::
export type AST = Element[]
export type Element = string | Placeholder
export type Placeholder = Plural | Styled | Typed | Simple
export type Plural = [ string, 'plural' | 'selectordinal', number, SubMessages ]
export type Styled = [ string, string, string | SubMessages ]
export type Typed = [ string, string ]
export type Simple = [ string ]
export type SubMessages = { [string]: AST }
export type Token = [ TokenType, string ]
export type TokenType = 'text' | 'space' | 'id' | 'type' | 'style' | 'offset' | 'number' | 'selector' | 'syntax'
type Context = {|
  pattern: string,
  index: number,
  tagsType: ?string,
  tokens: ?Token[]
|}
*/

var ARG_OPN = '{'
var ARG_CLS = '}'
var ARG_SEP = ','
var NUM_ARG = '#'
var TAG_OPN = '<'
var TAG_CLS = '>'
var TAG_END = '</'
var TAG_SELF_CLS = '/>'
var ESC = '\''
var OFFSET = 'offset:'
var simpleTypes = [
  'number',
  'date',
  'time',
  'ordinal',
  'duration',
  'spellout'
]
var submTypes = [
  'plural',
  'select',
  'selectordinal'
]

/**
 * parse
 *
 * Turns this:
 *  `You have { numBananas, plural,
 *       =0 {no bananas}
 *      one {a banana}
 *    other {# bananas}
 *  } for sale`
 *
 * into this:
 *  [ "You have ", [ "numBananas", "plural", 0, {
 *       "=0": [ "no bananas" ],
 *      "one": [ "a banana" ],
 *    "other": [ [ '#' ], " bananas" ]
 *  } ], " for sale." ]
 *
 * tokens:
 *  [
 *    [ "text", "You have " ],
 *    [ "syntax", "{" ],
 *    [ "space", " " ],
 *    [ "id", "numBananas" ],
 *    [ "syntax", ", " ],
 *    [ "space", " " ],
 *    [ "type", "plural" ],
 *    [ "syntax", "," ],
 *    [ "space", "\n     " ],
 *    [ "selector", "=0" ],
 *    [ "space", " " ],
 *    [ "syntax", "{" ],
 *    [ "text", "no bananas" ],
 *    [ "syntax", "}" ],
 *    [ "space", "\n    " ],
 *    [ "selector", "one" ],
 *    [ "space", " " ],
 *    [ "syntax", "{" ],
 *    [ "text", "a banana" ],
 *    [ "syntax", "}" ],
 *    [ "space", "\n  " ],
 *    [ "selector", "other" ],
 *    [ "space", " " ],
 *    [ "syntax", "{" ],
 *    [ "syntax", "#" ],
 *    [ "text", " bananas" ],
 *    [ "syntax", "}" ],
 *    [ "space", "\n" ],
 *    [ "syntax", "}" ],
 *    [ "text", " for sale." ]
 *  ]
 **/
exports = module.exports = function parse (
  pattern/*: string */,
  options/*:: ?: { tagsType?: string, tokens?: Token[] } */
)/*: AST */ {
  return parseAST({
    pattern: String(pattern),
    index: 0,
    tagsType: (options && options.tagsType) || null,
    tokens: (options && options.tokens) || null
  }, '')
}

function parseAST (current/*: Context */, parentType/*: string */)/*: AST */ {
  var pattern = current.pattern
  var length = pattern.length
  var elements/*: AST */ = []
  var start = current.index
  var text = parseText(current, parentType)
  if (text) elements.push(text)
  if (text && current.tokens) current.tokens.push([ 'text', pattern.slice(start, current.index) ])
  while (current.index < length) {
    if (pattern[current.index] === ARG_CLS) {
      if (!parentType) throw expected(current)
      break
    }
    if (parentType && current.tagsType && pattern.slice(current.index, current.index + TAG_END.length) === TAG_END) break
    elements.push(parsePlaceholder(current))
    start = current.index
    text = parseText(current, parentType)
    if (text) elements.push(text)
    if (text && current.tokens) current.tokens.push([ 'text', pattern.slice(start, current.index) ])
  }
  return elements
}

function parseText (current/*: Context */, parentType/*: string */)/*: string */ {
  var pattern = current.pattern
  var length = pattern.length
  var isHashSpecial = (parentType === 'plural' || parentType === 'selectordinal')
  var isAngleSpecial = !!current.tagsType
  var isArgStyle = (parentType === '{style}')
  var text = ''
  while (current.index < length) {
    var char = pattern[current.index]
    if (
      char === ARG_OPN || char === ARG_CLS ||
      (isHashSpecial && char === NUM_ARG) ||
      (isAngleSpecial && char === TAG_OPN) ||
      (isArgStyle && isWhitespace(char.charCodeAt(0)))
    ) {
      break
    } else if (char === ESC) {
      char = pattern[++current.index]
      if (char === ESC) { // double is always 1 '
        text += char
        ++current.index
      } else if (
        // only when necessary
        char === ARG_OPN || char === ARG_CLS ||
        (isHashSpecial && char === NUM_ARG) ||
        (isAngleSpecial && char === TAG_OPN) ||
        isArgStyle
      ) {
        text += char
        while (++current.index < length) {
          char = pattern[current.index]
          if (char === ESC && pattern[current.index + 1] === ESC) { // double is always 1 '
            text += ESC
            ++current.index
          } else if (char === ESC) { // end of quoted
            ++current.index
            break
          } else {
            text += char
          }
        }
      } else { // lone ' is just a '
        text += ESC
        // already incremented
      }
    } else {
      text += char
      ++current.index
    }
  }
  return text
}

function isWhitespace (code/*: number */)/*: boolean */ {
  return (
    (code >= 0x09 && code <= 0x0D) ||
    code === 0x20 || code === 0x85 || code === 0xA0 || code === 0x180E ||
    (code >= 0x2000 && code <= 0x200D) ||
    code === 0x2028 || code === 0x2029 || code === 0x202F || code === 0x205F ||
    code === 0x2060 || code === 0x3000 || code === 0xFEFF
  )
}

function skipWhitespace (current/*: Context */)/*: void */ {
  var pattern = current.pattern
  var length = pattern.length
  var start = current.index
  while (current.index < length && isWhitespace(pattern.charCodeAt(current.index))) {
    ++current.index
  }
  if (start < current.index && current.tokens) {
    current.tokens.push([ 'space', current.pattern.slice(start, current.index) ])
  }
}

function parsePlaceholder (current/*: Context */)/*: Placeholder */ {
  var pattern = current.pattern
  if (pattern[current.index] === NUM_ARG) {
    if (current.tokens) current.tokens.push([ 'syntax', NUM_ARG ])
    ++current.index // move passed #
    return [ NUM_ARG ]
  }

  var tag = parseTag(current)
  if (tag) return tag

  /* istanbul ignore if should be unreachable if parseAST and parseText are right */
  if (pattern[current.index] !== ARG_OPN) throw expected(current, ARG_OPN)
  if (current.tokens) current.tokens.push([ 'syntax', ARG_OPN ])
  ++current.index // move passed {
  skipWhitespace(current)

  var id = parseId(current)
  if (!id) throw expected(current, 'placeholder id')
  if (current.tokens) current.tokens.push([ 'id', id ])
  skipWhitespace(current)

  var char = pattern[current.index]
  if (char === ARG_CLS) { // end placeholder
    if (current.tokens) current.tokens.push([ 'syntax', ARG_CLS ])
    ++current.index // move passed }
    return [ id ]
  }

  if (char !== ARG_SEP) throw expected(current, ARG_SEP + ' or ' + ARG_CLS)
  if (current.tokens) current.tokens.push([ 'syntax', ARG_SEP ])
  ++current.index // move passed ,
  skipWhitespace(current)

  var type = parseId(current)
  if (!type) throw expected(current, 'placeholder type')
  if (current.tokens) current.tokens.push([ 'type', type ])
  skipWhitespace(current)
  char = pattern[current.index]
  if (char === ARG_CLS) { // end placeholder
    if (current.tokens) current.tokens.push([ 'syntax', ARG_CLS ])
    if (type === 'plural' || type === 'selectordinal' || type === 'select') {
      throw expected(current, type + ' sub-messages')
    }
    ++current.index // move passed }
    return [ id, type ]
  }

  if (char !== ARG_SEP) throw expected(current, ARG_SEP + ' or ' + ARG_CLS)
  if (current.tokens) current.tokens.push([ 'syntax', ARG_SEP ])
  ++current.index // move passed ,
  skipWhitespace(current)

  var arg
  if (type === 'plural' || type === 'selectordinal') {
    var offset = parsePluralOffset(current)
    skipWhitespace(current)
    arg = [ id, type, offset, parseSubMessages(current, type) ]
  } else if (type === 'select') {
    arg = [ id, type, parseSubMessages(current, type) ]
  } else if (simpleTypes.indexOf(type) >= 0) {
    arg = [ id, type, parseSimpleFormat(current) ]
  } else { // custom placeholder type
    var index = current.index
    var format/*: string | SubMessages */ = parseSimpleFormat(current)
    skipWhitespace(current)
    if (pattern[current.index] === ARG_OPN) {
      current.index = index // rewind, since should have been submessages
      format = parseSubMessages(current, type)
    }
    arg = [ id, type, format ]
  }

  skipWhitespace(current)
  if (pattern[current.index] !== ARG_CLS) throw expected(current, ARG_CLS)
  if (current.tokens) current.tokens.push([ 'syntax', ARG_CLS ])
  ++current.index // move passed }
  return arg
}

function parseTag (current/*: Context */)/*: ?Placeholder */ {
  var tagsType = current.tagsType
  if (!tagsType || current.pattern[current.index] !== TAG_OPN) return

  if (current.pattern.slice(current.index, current.index + TAG_END.length) === TAG_END) {
    throw expected(current, null, 'closing tag without matching opening tag')
  }
  if (current.tokens) current.tokens.push([ 'syntax', TAG_OPN ])
  ++current.index // move passed <

  var id = parseId(current, true)
  if (!id) throw expected(current, 'placeholder id')
  if (current.tokens) current.tokens.push([ 'id', id ])
  skipWhitespace(current)

  if (current.pattern.slice(current.index, current.index + TAG_SELF_CLS.length) === TAG_SELF_CLS) {
    if (current.tokens) current.tokens.push([ 'syntax', TAG_SELF_CLS ])
    current.index += TAG_SELF_CLS.length
    return [ id, tagsType ]
  }
  if (current.pattern[current.index] !== TAG_CLS) throw expected(current, TAG_CLS)
  if (current.tokens) current.tokens.push([ 'syntax', TAG_CLS ])
  ++current.index // move passed >

  var children = parseAST(current, tagsType)

  var end = current.index
  if (current.pattern.slice(current.index, current.index + TAG_END.length) !== TAG_END) throw expected(current, TAG_END + id + TAG_CLS)
  if (current.tokens) current.tokens.push([ 'syntax', TAG_END ])
  current.index += TAG_END.length
  var closeId = parseId(current, true)
  if (closeId && current.tokens) current.tokens.push([ 'id', closeId ])
  if (id !== closeId) {
    current.index = end // rewind for better error message
    throw expected(current, TAG_END + id + TAG_CLS, TAG_END + closeId + TAG_CLS)
  }
  skipWhitespace(current)
  if (current.pattern[current.index] !== TAG_CLS) throw expected(current, TAG_CLS)
  if (current.tokens) current.tokens.push([ 'syntax', TAG_CLS ])
  ++current.index // move passed >

  return [ id, tagsType, { children: children } ]
}

function parseId (current/*: Context */, isTag/*:: ?: boolean */)/*: string */ {
  var pattern = current.pattern
  var length = pattern.length
  var id = ''
  while (current.index < length) {
    var char = pattern[current.index]
    if (
      char === ARG_OPN || char === ARG_CLS || char === ARG_SEP ||
      char === NUM_ARG || char === ESC || isWhitespace(char.charCodeAt(0)) ||
      (isTag && (char === TAG_OPN || char === TAG_CLS || char === '/'))
    ) break
    id += char
    ++current.index
  }
  return id
}

function parseSimpleFormat (current/*: Context */)/*: string */ {
  var start = current.index
  var style = parseText(current, '{style}')
  if (!style) throw expected(current, 'placeholder style name')
  if (current.tokens) current.tokens.push([ 'style', current.pattern.slice(start, current.index) ])
  return style
}

function parsePluralOffset (current/*: Context */)/*: number */ {
  var pattern = current.pattern
  var length = pattern.length
  var offset = 0
  if (pattern.slice(current.index, current.index + OFFSET.length) === OFFSET) {
    if (current.tokens) current.tokens.push([ 'offset', 'offset' ], [ 'syntax', ':' ])
    current.index += OFFSET.length // move passed offset:
    skipWhitespace(current)
    var start = current.index
    while (current.index < length && isDigit(pattern.charCodeAt(current.index))) {
      ++current.index
    }
    if (start === current.index) throw expected(current, 'offset number')
    if (current.tokens) current.tokens.push([ 'number', pattern.slice(start, current.index) ])
    offset = +pattern.slice(start, current.index)
  }
  return offset
}

function isDigit (code/*: number */)/*: boolean */ {
  return (code >= 0x30 && code <= 0x39)
}

function parseSubMessages (current/*: Context */, parentType/*: string */)/*: SubMessages */ {
  var pattern = current.pattern
  var length = pattern.length
  var options/*: SubMessages */ = {}
  while (current.index < length && pattern[current.index] !== ARG_CLS) {
    var selector = parseId(current)
    if (!selector) throw expected(current, 'sub-message selector')
    if (current.tokens) current.tokens.push([ 'selector', selector ])
    skipWhitespace(current)
    options[selector] = parseSubMessage(current, parentType)
    skipWhitespace(current)
  }
  if (!options.other && submTypes.indexOf(parentType) >= 0) {
    throw expected(current, null, null, '"other" sub-message must be specified in ' + parentType)
  }
  return options
}

function parseSubMessage (current/*: Context */, parentType/*: string */)/*: AST */ {
  if (current.pattern[current.index] !== ARG_OPN) throw expected(current, ARG_OPN + ' to start sub-message')
  if (current.tokens) current.tokens.push([ 'syntax', ARG_OPN ])
  ++current.index // move passed {
  var message = parseAST(current, parentType)
  if (current.pattern[current.index] !== ARG_CLS) throw expected(current, ARG_CLS + ' to end sub-message')
  if (current.tokens) current.tokens.push([ 'syntax', ARG_CLS ])
  ++current.index // move passed }
  return message
}

function expected (current/*: Context */, expected/*:: ?: ?string */, found/*:: ?: ?string */, message/*:: ?: string */) {
  var pattern = current.pattern
  var lines = pattern.slice(0, current.index).split(/\r?\n/)
  var offset = current.index
  var line = lines.length
  var column = lines.slice(-1)[0].length
  found = found || (
    (current.index >= pattern.length) ? 'end of message pattern'
      : (parseId(current) || pattern[current.index])
  )
  if (!message) message = errorMessage(expected, found)
  message += ' in ' + pattern.replace(/\r?\n/g, '\n')
  return new SyntaxError(message, expected, found, offset, line, column)
}

function errorMessage (expected/*: ?string */, found/* string */) {
  if (!expected) return 'Unexpected ' + found + ' found'
  return 'Expected ' + expected + ' but found ' + found
}

/**
 * SyntaxError
 *  Holds information about bad syntax found in a message pattern
 **/
function SyntaxError (message/*: string */, expected/*: ?string */, found/*: ?string */, offset/*: number */, line/*: number */, column/*: number */) {
  Error.call(this, message)
  this.name = 'SyntaxError'
  this.message = message
  this.expected = expected
  this.found = found
  this.offset = offset
  this.line = line
  this.column = column
}
SyntaxError.prototype = Object.create(Error.prototype)
exports.SyntaxError = SyntaxError


// @flow
'use strict'
var interpret = require('format-message-interpret')
var parse = require('format-message-parse')
var plurals = require('format-message-interpret/plurals')
var supportedExp = new RegExp(
  '^(' + Object.keys(plurals).join('|') + ')\\b'
)

/*::
import type { Types } from 'format-message-interpret'
import type { AST } from 'format-message-parse'
type Options = {
  types: Types
}
type Internals = {
  ast: AST,
  format: (args?: Object) => string,
  locale: string,
  locales?: string | string[],
  toParts?: (args?: Object) => any[],
  options?: Options
}
*/

var internals/*: WeakMap<Object, Internals> */ = new WeakMap()

/*!
 * Intl.MessageFormat prollyfill
 * Copyright(c) 2015 Andy VanWagoner
 * MIT licensed
 **/
function MessageFormat (
  pattern/*: string */,
  locales/*:: ?: string | string[] */,
  options/*:: ?: Options */
) {
  if (!(this instanceof MessageFormat) || internals.has(this)) {
    throw new TypeError('calling MessageFormat constructor without new is invalid')
  }
  var ast = parse(pattern)
  internals.set(this, {
    ast: ast,
    format: interpret(ast, locales, options && options.types),
    locale: MessageFormat.supportedLocalesOf(locales)[0] || 'en',
    locales: locales,
    options: options
  })
}
module.exports = MessageFormat

// $FlowFixMe It thinks `value` needs to be defined for format
Object.defineProperties(MessageFormat.prototype, {
  format: {
    configurable: true,
    get: function format () {
      var values = internals.get(this)
      if (!values) throw new TypeError('MessageFormat.prototype.format called on value that\'s not an object initialized as a MessageFormat')
      return values.format
    }
  },
  formatToParts: {
    configurable: true,
    writable: true,
    value: function formatToParts (args/*:: ?: Object */) {
      var values = internals.get(this)
      if (!values) throw new TypeError('MessageFormat.prototype.formatToParts called on value that\'s not an object initialized as a MessageFormat')
      var frmt = values.toParts || (values.toParts = interpret.toParts(
        values.ast,
        values.locales,
        values.options && values.options.types
      ))
      return frmt(args)
    }
  },
  resolvedOptions: {
    configurable: true,
    writable: true,
    value: function resolvedOptions () {
      var values = internals.get(this)
      if (!values) throw new TypeError('MessageFormat.prototype.resolvedOptions called on value that\'s not an object initialized as a MessageFormat')
      return {
        locale: values.locale
      }
    }
  }
})

/* istanbul ignore else */
if (typeof Symbol !== 'undefined') {
  Object.defineProperty(MessageFormat.prototype, Symbol.toStringTag, { value: 'Object' })
}

Object.defineProperties(MessageFormat, {
  supportedLocalesOf: {
    configurable: true,
    writable: true,
    value: function supportedLocalesOf (requestedLocales/*:: ?: string | string[] */) {
      return [].concat(
        Intl.NumberFormat.supportedLocalesOf(requestedLocales),
        Intl.DateTimeFormat.supportedLocalesOf(requestedLocales),
        Intl.PluralRules ? Intl.PluralRules.supportedLocalesOf(requestedLocales) : [],
        [].concat(requestedLocales || []).filter(function (locale) {
          return supportedExp.test(locale)
        })
      ).filter(function (v, i, a) { return a.indexOf(v) === i })
    }
  }
})


(function() {
  var B, BL, BR, Cnk, Cube, D, DB, DBL, DF, DFR, DL, DLF, DR, DRB, F, FL, FR, Include, L, N_FLIP, N_FRtoBR, N_PARITY, N_SLICE1, N_SLICE2, N_TWIST, N_UBtoDF, N_URFtoDLF, N_URtoDF, N_URtoUL, R, U, UB, UBR, UF, UFL, UL, ULB, UR, URF, allMoves1, allMoves2, computeMoveTable, computePruningTable, faceNames, faceNums, factorial, key, max, mergeURtoDF, moveTableParams, nextMoves1, nextMoves2, permutationIndex, pruning, pruningTableParams, rotateLeft, rotateRight, value,
    indexOf = [].indexOf;

  Cube = this.Cube || require('./cube');

  // Centers
  [U, R, F, D, L, B] = [0, 1, 2, 3, 4, 5];

  // Corners
  [URF, UFL, ULB, UBR, DFR, DLF, DBL, DRB] = [0, 1, 2, 3, 4, 5, 6, 7];

  // Edges
  [UR, UF, UL, UB, DR, DF, DL, DB, FR, FL, BL, BR] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

  //# Helpers

  // n choose k, i.e. the binomial coeffiecient
  Cnk = function(n, k) {
    var i, j, s;
    if (n < k) {
      return 0;
    }
    if (k > n / 2) {
      k = n - k;
    }
    s = 1;
    i = n;
    j = 1;
    while (i !== n - k) {
      s *= i;
      s /= j;
      i--;
      j++;
    }
    return s;
  };

  // n!
  factorial = function(n) {
    var f, i, m, ref;
    f = 1;
    for (i = m = 2, ref = n; (2 <= ref ? m <= ref : m >= ref); i = 2 <= ref ? ++m : --m) {
      f *= i;
    }
    return f;
  };

  // Maximum of two values
  max = function(a, b) {
    if (a > b) {
      return a;
    } else {
      return b;
    }
  };

  // Rotate elements between l and r left by one place
  rotateLeft = function(array, l, r) {
    var i, m, ref, ref1, tmp;
    tmp = array[l];
    for (i = m = ref = l, ref1 = r - 1; (ref <= ref1 ? m <= ref1 : m >= ref1); i = ref <= ref1 ? ++m : --m) {
      array[i] = array[i + 1];
    }
    return array[r] = tmp;
  };

  // Rotate elements between l and r right by one place
  rotateRight = function(array, l, r) {
    var i, m, ref, ref1, tmp;
    tmp = array[r];
    for (i = m = ref = r, ref1 = l + 1; (ref <= ref1 ? m <= ref1 : m >= ref1); i = ref <= ref1 ? ++m : --m) {
      array[i] = array[i - 1];
    }
    return array[l] = tmp;
  };

  // Generate a function that computes permutation indices.

  // The permutation index actually encodes two indices: Combination,
  // i.e. positions of the cubies start..end (A) and their respective
  // permutation (B). The maximum value for B is

  //   maxB = (end - start + 1)!

  // and the index is A * maxB + B
  permutationIndex = function(context, start, end, fromEnd = false) {
    var i, maxAll, maxB, maxOur, our, permName;
    maxOur = end - start;
    maxB = factorial(maxOur + 1);
    if (context === 'corners') {
      maxAll = 7;
      permName = 'cp';
    } else {
      maxAll = 11;
      permName = 'ep';
    }
    our = (function() {
      var m, ref, results;
      results = [];
      for (i = m = 0, ref = maxOur; (0 <= ref ? m <= ref : m >= ref); i = 0 <= ref ? ++m : --m) {
        results.push(0);
      }
      return results;
    })();
    return function(index) {
      var a, b, c, j, k, m, o, p, perm, q, ref, ref1, ref10, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, t, u, w, x, y, z;
      if (index != null) {
        for (i = m = 0, ref = maxOur; (0 <= ref ? m <= ref : m >= ref); i = 0 <= ref ? ++m : --m) {
          // Reset our to [start..end]
          our[i] = i + start;
        }
        b = index % maxB; // permutation
        a = index / maxB | 0; // combination

        // Invalidate all edges
        perm = this[permName];
        for (i = o = 0, ref1 = maxAll; (0 <= ref1 ? o <= ref1 : o >= ref1); i = 0 <= ref1 ? ++o : --o) {
          perm[i] = -1;
        }
// Generate permutation from index b
        for (j = p = 1, ref2 = maxOur; (1 <= ref2 ? p <= ref2 : p >= ref2); j = 1 <= ref2 ? ++p : --p) {
          k = b % (j + 1);
          b = b / (j + 1) | 0;
          // TODO: Implement rotateRightBy(our, 0, j, k)
          while (k > 0) {
            rotateRight(our, 0, j);
            k--;
          }
        }
        // Generate combination and set our edges
        x = maxOur;
        if (fromEnd) {
          for (j = q = 0, ref3 = maxAll; (0 <= ref3 ? q <= ref3 : q >= ref3); j = 0 <= ref3 ? ++q : --q) {
            c = Cnk(maxAll - j, x + 1);
            if (a - c >= 0) {
              perm[j] = our[maxOur - x];
              a -= c;
              x--;
            }
          }
        } else {
          for (j = t = ref4 = maxAll; (ref4 <= 0 ? t <= 0 : t >= 0); j = ref4 <= 0 ? ++t : --t) {
            c = Cnk(j, x + 1);
            if (a - c >= 0) {
              perm[j] = our[x];
              a -= c;
              x--;
            }
          }
        }
        return this;
      } else {
        perm = this[permName];
        for (i = u = 0, ref5 = maxOur; (0 <= ref5 ? u <= ref5 : u >= ref5); i = 0 <= ref5 ? ++u : --u) {
          our[i] = -1;
        }
        a = b = x = 0;
        // Compute the index a < ((maxAll + 1) choose (maxOur + 1)) and
        // the permutation
        if (fromEnd) {
          for (j = w = ref6 = maxAll; (ref6 <= 0 ? w <= 0 : w >= 0); j = ref6 <= 0 ? ++w : --w) {
            if ((start <= (ref7 = perm[j]) && ref7 <= end)) {
              a += Cnk(maxAll - j, x + 1);
              our[maxOur - x] = perm[j];
              x++;
            }
          }
        } else {
          for (j = y = 0, ref8 = maxAll; (0 <= ref8 ? y <= ref8 : y >= ref8); j = 0 <= ref8 ? ++y : --y) {
            if ((start <= (ref9 = perm[j]) && ref9 <= end)) {
              a += Cnk(j, x + 1);
              our[x] = perm[j];
              x++;
            }
          }
        }
// Compute the index b < (maxOur + 1)! for the permutation
        for (j = z = ref10 = maxOur; (ref10 <= 0 ? z <= 0 : z >= 0); j = ref10 <= 0 ? ++z : --z) {
          k = 0;
          while (our[j] !== start + j) {
            rotateLeft(our, 0, j);
            k++;
          }
          b = (j + 1) * b + k;
        }
        return a * maxB + b;
      }
    };
  };

  Include = {
    // The twist of the 8 corners, 0 <= twist < 3^7. The orientation of
    // the DRB corner is fully determined by the orientation of the other
    // corners.
    twist: function(twist) {
      var i, m, o, ori, parity, v;
      if (twist != null) {
        parity = 0;
        for (i = m = 6; m >= 0; i = --m) {
          ori = twist % 3;
          twist = (twist / 3) | 0;
          this.co[i] = ori;
          parity += ori;
        }
        this.co[7] = (3 - parity % 3) % 3;
        return this;
      } else {
        v = 0;
        for (i = o = 0; o <= 6; i = ++o) {
          v = 3 * v + this.co[i];
        }
        return v;
      }
    },
    // The flip of the 12 edges, 0 <= flip < 2^11. The orientation of the
    // BR edge is fully determined by the orientation of the other edges.
    flip: function(flip) {
      var i, m, o, ori, parity, v;
      if (flip != null) {
        parity = 0;
        for (i = m = 10; m >= 0; i = --m) {
          ori = flip % 2;
          flip = flip / 2 | 0;
          this.eo[i] = ori;
          parity += ori;
        }
        this.eo[11] = (2 - parity % 2) % 2;
        return this;
      } else {
        v = 0;
        for (i = o = 0; o <= 10; i = ++o) {
          v = 2 * v + this.eo[i];
        }
        return v;
      }
    },
    // Parity of the corner permutation
    cornerParity: function() {
      var i, j, m, o, ref, ref1, ref2, ref3, s;
      s = 0;
      for (i = m = ref = DRB, ref1 = URF + 1; (ref <= ref1 ? m <= ref1 : m >= ref1); i = ref <= ref1 ? ++m : --m) {
        for (j = o = ref2 = i - 1, ref3 = URF; (ref2 <= ref3 ? o <= ref3 : o >= ref3); j = ref2 <= ref3 ? ++o : --o) {
          if (this.cp[j] > this.cp[i]) {
            s++;
          }
        }
      }
      return s % 2;
    },
    // Parity of the edges permutation. Parity of corners and edges are
    // the same if the cube is solvable.
    edgeParity: function() {
      var i, j, m, o, ref, ref1, ref2, ref3, s;
      s = 0;
      for (i = m = ref = BR, ref1 = UR + 1; (ref <= ref1 ? m <= ref1 : m >= ref1); i = ref <= ref1 ? ++m : --m) {
        for (j = o = ref2 = i - 1, ref3 = UR; (ref2 <= ref3 ? o <= ref3 : o >= ref3); j = ref2 <= ref3 ? ++o : --o) {
          if (this.ep[j] > this.ep[i]) {
            s++;
          }
        }
      }
      return s % 2;
    },
    // Permutation of the six corners URF, UFL, ULB, UBR, DFR, DLF
    URFtoDLF: permutationIndex('corners', URF, DLF),
    // Permutation of the three edges UR, UF, UL
    URtoUL: permutationIndex('edges', UR, UL),
    // Permutation of the three edges UB, DR, DF
    UBtoDF: permutationIndex('edges', UB, DF),
    // Permutation of the six edges UR, UF, UL, UB, DR, DF
    URtoDF: permutationIndex('edges', UR, DF),
    // Permutation of the equator slice edges FR, FL, BL and BR
    FRtoBR: permutationIndex('edges', FR, BR, true)
  };

  for (key in Include) {
    value = Include[key];
    Cube.prototype[key] = value;
  }

  computeMoveTable = function(context, coord, size) {
    var apply, cube, i, inner, j, k, m, move, o, p, ref, results;
    // Loop through all valid values for the coordinate, setting cube's
    // state in each iteration. Then apply each of the 18 moves to the
    // cube, and compute the resulting coordinate.
    apply = context === 'corners' ? 'cornerMultiply' : 'edgeMultiply';
    cube = new Cube;
    results = [];
    for (i = m = 0, ref = size - 1; (0 <= ref ? m <= ref : m >= ref); i = 0 <= ref ? ++m : --m) {
      cube[coord](i);
      inner = [];
      for (j = o = 0; o <= 5; j = ++o) {
        move = Cube.moves[j];
        for (k = p = 0; p <= 2; k = ++p) {
          cube[apply](move);
          inner.push(cube[coord]());
        }
        // 4th face turn restores the cube
        cube[apply](move);
      }
      results.push(inner);
    }
    return results;
  };

  // Because we only have the phase 2 URtoDF coordinates, we need to
  // merge the URtoUL and UBtoDF coordinates to URtoDF in the beginning
  // of phase 2.
  mergeURtoDF = (function() {
    var a, b;
    a = new Cube;
    b = new Cube;
    return function(URtoUL, UBtoDF) {
      var i, m;
      // Collisions can be found because unset are set to -1
      a.URtoUL(URtoUL);
      b.UBtoDF(UBtoDF);
      for (i = m = 0; m <= 7; i = ++m) {
        if (a.ep[i] !== -1) {
          if (b.ep[i] !== -1) {
            return -1; // collision
          } else {
            b.ep[i] = a.ep[i];
          }
        }
      }
      return b.URtoDF();
    };
  })();

  N_TWIST = 2187; // 3^7 corner orientations

  N_FLIP = 2048; // 2^11 possible edge flips

  N_PARITY = 2; // 2 possible parities

  N_FRtoBR = 11880; // 12!/(12-4)! permutations of FR..BR edges

  N_SLICE1 = 495; // (12 choose 4) possible positions of FR..BR edges

  N_SLICE2 = 24; // 4! permutations of FR..BR edges in phase 2

  N_URFtoDLF = 20160; // 8!/(8-6)! permutations of URF..DLF corners

  // The URtoDF move table is only computed for phase 2 because the full
  // table would have >650000 entries
  N_URtoDF = 20160; // 8!/(8-6)! permutation of UR..DF edges in phase 2

  N_URtoUL = 1320; // 12!/(12-3)! permutations of UR..UL edges

  N_UBtoDF = 1320; // 12!/(12-3)! permutations of UB..DF edges

  // The move table for parity is so small that it's included here
  Cube.moveTables = {
    parity: [[1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1], [0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]],
    twist: null,
    flip: null,
    FRtoBR: null,
    URFtoDLF: null,
    URtoDF: null,
    URtoUL: null,
    UBtoDF: null,
    mergeURtoDF: null
  };

  // Other move tables are computed on the fly
  moveTableParams = {
    // name: [scope, size]
    twist: ['corners', N_TWIST],
    flip: ['edges', N_FLIP],
    FRtoBR: ['edges', N_FRtoBR],
    URFtoDLF: ['corners', N_URFtoDLF],
    URtoDF: ['edges', N_URtoDF],
    URtoUL: ['edges', N_URtoUL],
    UBtoDF: ['edges', N_UBtoDF],
    mergeURtoDF: []
  };

  Cube.computeMoveTables = function(...tables) {
    var len, m, name, scope, size, tableName;
    if (tables.length === 0) {
      tables = (function() {
        var results;
        results = [];
        for (name in moveTableParams) {
          results.push(name);
        }
        return results;
      })();
    }
    for (m = 0, len = tables.length; m < len; m++) {
      tableName = tables[m];
      if (this.moveTables[tableName] !== null) {
        // Already computed
        continue;
      }
      if (tableName === 'mergeURtoDF') {
        this.moveTables.mergeURtoDF = (function() {
          var UBtoDF, URtoUL, o, results;
          results = [];
          for (URtoUL = o = 0; o <= 335; URtoUL = ++o) {
            results.push((function() {
              var p, results1;
              results1 = [];
              for (UBtoDF = p = 0; p <= 335; UBtoDF = ++p) {
                results1.push(mergeURtoDF(URtoUL, UBtoDF));
              }
              return results1;
            })());
          }
          return results;
        })();
      } else {
        [scope, size] = moveTableParams[tableName];
        this.moveTables[tableName] = computeMoveTable(scope, tableName, size);
      }
    }
    return this;
  };

  // Phase 1: All moves are valid
  allMoves1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];

  // The list of next valid phase 1 moves when the given face was turned
  // in the last move
  nextMoves1 = (function() {
    var face, lastFace, m, next, o, p, power, results;
    results = [];
    for (lastFace = m = 0; m <= 5; lastFace = ++m) {
      next = [];
// Don't allow commuting moves, e.g. U U'. Also make sure that
// opposite faces are always moved in the same order, i.e. allow
// U D but no D U. This avoids sequences like U D U'.
      for (face = o = 0; o <= 5; face = ++o) {
        if (face !== lastFace && face !== lastFace - 3) {
// single, double or inverse move
          for (power = p = 0; p <= 2; power = ++p) {
            next.push(face * 3 + power);
          }
        }
      }
      results.push(next);
    }
    return results;
  })();

  // Phase 2: Double moves of all faces plus quarter moves of U and D
  allMoves2 = [0, 1, 2, 4, 7, 9, 10, 11, 13, 16];

  nextMoves2 = (function() {
    var face, lastFace, len, m, next, o, p, power, powers, results;
    results = [];
    for (lastFace = m = 0; m <= 5; lastFace = ++m) {
      next = [];
      for (face = o = 0; o <= 5; face = ++o) {
        if (!(face !== lastFace && face !== lastFace - 3)) {
          continue;
        }
        // Allow all moves of U and D and double moves of others
        powers = face === 0 || face === 3 ? [0, 1, 2] : [1];
        for (p = 0, len = powers.length; p < len; p++) {
          power = powers[p];
          next.push(face * 3 + power);
        }
      }
      results.push(next);
    }
    return results;
  })();

  // 8 values are encoded in one number
  pruning = function(table, index, value) {
    var pos, shift, slot;
    pos = index % 8;
    slot = index >> 3;
    shift = pos << 2;
    if (value != null) {
      // Set
      table[slot] &= ~(0xF << shift);
      table[slot] |= value << shift;
      return value;
    } else {
      // Get
      return (table[slot] & (0xF << shift)) >>> shift;
    }
  };

  computePruningTable = function(phase, size, currentCoords, nextIndex) {
    var current, depth, done, index, len, m, move, moves, next, o, ref, table, x;
    // Initialize all values to 0xF
    table = (function() {
      var m, ref, results;
      results = [];
      for (x = m = 0, ref = Math.ceil(size / 8) - 1; (0 <= ref ? m <= ref : m >= ref); x = 0 <= ref ? ++m : --m) {
        results.push(0xFFFFFFFF);
      }
      return results;
    })();
    if (phase === 1) {
      moves = allMoves1;
    } else {
      moves = allMoves2;
    }
    depth = 0;
    pruning(table, 0, depth);
    done = 1;
    // In each iteration, take each state found in the previous depth and
    // compute the next state. Stop when all states have been assigned a
    // depth.
    while (done !== size) {
      for (index = m = 0, ref = size - 1; (0 <= ref ? m <= ref : m >= ref); index = 0 <= ref ? ++m : --m) {
        if (!(pruning(table, index) === depth)) {
          continue;
        }
        current = currentCoords(index);
        for (o = 0, len = moves.length; o < len; o++) {
          move = moves[o];
          next = nextIndex(current, move);
          if (pruning(table, next) === 0xF) {
            pruning(table, next, depth + 1);
            done++;
          }
        }
      }
      depth++;
    }
    return table;
  };

  Cube.pruningTables = {
    sliceTwist: null,
    sliceFlip: null,
    sliceURFtoDLFParity: null,
    sliceURtoDFParity: null
  };

  pruningTableParams = {
    // name: [phase, size, currentCoords, nextIndex]
    sliceTwist: [
      1,
      N_SLICE1 * N_TWIST,
      function(index) {
        return [index % N_SLICE1,
      index / N_SLICE1 | 0];
      },
      function(current,
      move) {
        var newSlice,
      newTwist,
      slice,
      twist;
        [slice,
      twist] = current;
        newSlice = Cube.moveTables.FRtoBR[slice * 24][move] / 24 | 0;
        newTwist = Cube.moveTables.twist[twist][move];
        return newTwist * N_SLICE1 + newSlice;
      }
    ],
    sliceFlip: [
      1,
      N_SLICE1 * N_FLIP,
      function(index) {
        return [index % N_SLICE1,
      index / N_SLICE1 | 0];
      },
      function(current,
      move) {
        var flip,
      newFlip,
      newSlice,
      slice;
        [slice,
      flip] = current;
        newSlice = Cube.moveTables.FRtoBR[slice * 24][move] / 24 | 0;
        newFlip = Cube.moveTables.flip[flip][move];
        return newFlip * N_SLICE1 + newSlice;
      }
    ],
    sliceURFtoDLFParity: [
      2,
      N_SLICE2 * N_URFtoDLF * N_PARITY,
      function(index) {
        return [index % 2,
      (index / 2 | 0) % N_SLICE2,
      (index / 2 | 0) / N_SLICE2 | 0];
      },
      function(current,
      move) {
        var URFtoDLF,
      newParity,
      newSlice,
      newURFtoDLF,
      parity,
      slice;
        [parity,
      slice,
      URFtoDLF] = current;
        newParity = Cube.moveTables.parity[parity][move];
        newSlice = Cube.moveTables.FRtoBR[slice][move];
        newURFtoDLF = Cube.moveTables.URFtoDLF[URFtoDLF][move];
        return (newURFtoDLF * N_SLICE2 + newSlice) * 2 + newParity;
      }
    ],
    sliceURtoDFParity: [
      2,
      N_SLICE2 * N_URtoDF * N_PARITY,
      function(index) {
        return [index % 2,
      (index / 2 | 0) % N_SLICE2,
      (index / 2 | 0) / N_SLICE2 | 0];
      },
      function(current,
      move) {
        var URtoDF,
      newParity,
      newSlice,
      newURtoDF,
      parity,
      slice;
        [parity,
      slice,
      URtoDF] = current;
        newParity = Cube.moveTables.parity[parity][move];
        newSlice = Cube.moveTables.FRtoBR[slice][move];
        newURtoDF = Cube.moveTables.URtoDF[URtoDF][move];
        return (newURtoDF * N_SLICE2 + newSlice) * 2 + newParity;
      }
    ]
  };

  Cube.computePruningTables = function(...tables) {
    var len, m, name, params, tableName;
    if (tables.length === 0) {
      tables = (function() {
        var results;
        results = [];
        for (name in pruningTableParams) {
          results.push(name);
        }
        return results;
      })();
    }
    for (m = 0, len = tables.length; m < len; m++) {
      tableName = tables[m];
      if (this.pruningTables[tableName] !== null) {
        // Already computed
        continue;
      }
      params = pruningTableParams[tableName];
      this.pruningTables[tableName] = computePruningTable(...params);
    }
    return this;
  };

  Cube.initSolver = function() {
    Cube.computeMoveTables();
    return Cube.computePruningTables();
  };

  Cube.prototype.solveUpright = function(maxDepth = 22) {
    var State, freeStates, moveNames, phase1, phase1search, phase2, phase2search, solution, state, x;
    // Names for all moves, i.e. U, U2, U', F, F2, ...
    moveNames = (function() {
      var face, faceName, m, o, power, powerName, result;
      faceName = ['U', 'R', 'F', 'D', 'L', 'B'];
      powerName = ['', '2', "'"];
      result = [];
      for (face = m = 0; m <= 5; face = ++m) {
        for (power = o = 0; o <= 2; power = ++o) {
          result.push(faceName[face] + powerName[power]);
        }
      }
      return result;
    })();
    State = class State {
      constructor(cube) {
        this.parent = null;
        this.lastMove = null;
        this.depth = 0;
        if (cube) {
          this.init(cube);
        }
      }

      init(cube) {
        // Phase 1 coordinates
        this.flip = cube.flip();
        this.twist = cube.twist();
        this.slice = cube.FRtoBR() / N_SLICE2 | 0;
        // Phase 2 coordinates
        this.parity = cube.cornerParity();
        this.URFtoDLF = cube.URFtoDLF();
        this.FRtoBR = cube.FRtoBR();
        // These are later merged to URtoDF when phase 2 begins
        this.URtoUL = cube.URtoUL();
        this.UBtoDF = cube.UBtoDF();
        return this;
      }

      solution() {
        if (this.parent) {
          return this.parent.solution() + moveNames[this.lastMove] + ' ';
        } else {
          return '';
        }
      }

      //# Helpers
      move(table, index, move) {
        return Cube.moveTables[table][index][move];
      }

      pruning(table, index) {
        return pruning(Cube.pruningTables[table], index);
      }

      //# Phase 1

      // Return the next valid phase 1 moves for this state
      moves1() {
        if (this.lastMove !== null) {
          return nextMoves1[this.lastMove / 3 | 0];
        } else {
          return allMoves1;
        }
      }

      // Compute the minimum number of moves to the end of phase 1
      minDist1() {
        var d1, d2;
        // The maximum number of moves to the end of phase 1 wrt. the
        // combination flip and slice coordinates only
        d1 = this.pruning('sliceFlip', N_SLICE1 * this.flip + this.slice);
        // The combination of twist and slice coordinates
        d2 = this.pruning('sliceTwist', N_SLICE1 * this.twist + this.slice);
        // The true minimal distance is the maximum of these two
        return max(d1, d2);
      }

      // Compute the next phase 1 state for the given move
      next1(move) {
        var next;
        next = freeStates.pop();
        next.parent = this;
        next.lastMove = move;
        next.depth = this.depth + 1;
        next.flip = this.move('flip', this.flip, move);
        next.twist = this.move('twist', this.twist, move);
        next.slice = this.move('FRtoBR', this.slice * 24, move) / 24 | 0;
        return next;
      }

      //# Phase 2

      // Return the next valid phase 2 moves for this state
      moves2() {
        if (this.lastMove !== null) {
          return nextMoves2[this.lastMove / 3 | 0];
        } else {
          return allMoves2;
        }
      }

      // Compute the minimum number of moves to the solved cube
      minDist2() {
        var d1, d2, index1, index2;
        index1 = (N_SLICE2 * this.URtoDF + this.FRtoBR) * N_PARITY + this.parity;
        d1 = this.pruning('sliceURtoDFParity', index1);
        index2 = (N_SLICE2 * this.URFtoDLF + this.FRtoBR) * N_PARITY + this.parity;
        d2 = this.pruning('sliceURFtoDLFParity', index2);
        return max(d1, d2);
      }

      // Initialize phase 2 coordinates
      init2(top = true) {
        if (this.parent === null) {
          return;
        }
        // For other states, the phase 2 state is computed based on
        // parent's state.
        // Already assigned for the initial state
        this.parent.init2(false);
        this.URFtoDLF = this.move('URFtoDLF', this.parent.URFtoDLF, this.lastMove);
        this.FRtoBR = this.move('FRtoBR', this.parent.FRtoBR, this.lastMove);
        this.parity = this.move('parity', this.parent.parity, this.lastMove);
        this.URtoUL = this.move('URtoUL', this.parent.URtoUL, this.lastMove);
        this.UBtoDF = this.move('UBtoDF', this.parent.UBtoDF, this.lastMove);
        if (top) {
          // This is the initial phase 2 state. Get the URtoDF coordinate
          // by merging URtoUL and UBtoDF
          return this.URtoDF = this.move('mergeURtoDF', this.URtoUL, this.UBtoDF);
        }
      }

      // Compute the next phase 2 state for the given move
      next2(move) {
        var next;
        next = freeStates.pop();
        next.parent = this;
        next.lastMove = move;
        next.depth = this.depth + 1;
        next.URFtoDLF = this.move('URFtoDLF', this.URFtoDLF, move);
        next.FRtoBR = this.move('FRtoBR', this.FRtoBR, move);
        next.parity = this.move('parity', this.parity, move);
        next.URtoDF = this.move('URtoDF', this.URtoDF, move);
        return next;
      }

    };
    solution = null;
    phase1search = function(state) {
      var depth, m, ref, results;
      depth = 0;
      results = [];
      for (depth = m = 1, ref = maxDepth; (1 <= ref ? m <= ref : m >= ref); depth = 1 <= ref ? ++m : --m) {
        phase1(state, depth);
        if (solution !== null) {
          break;
        }
        results.push(depth++);
      }
      return results;
    };
    phase1 = function(state, depth) {
      var len, m, move, next, ref, ref1, results;
      if (depth === 0) {
        if (state.minDist1() === 0) {
          // Make sure we don't start phase 2 with a phase 2 move as the
          // last move in phase 1, because phase 2 would then repeat the
          // same move.
          if (state.lastMove === null || (ref = state.lastMove, indexOf.call(allMoves2, ref) < 0)) {
            return phase2search(state);
          }
        }
      } else if (depth > 0) {
        if (state.minDist1() <= depth) {
          ref1 = state.moves1();
          results = [];
          for (m = 0, len = ref1.length; m < len; m++) {
            move = ref1[m];
            next = state.next1(move);
            phase1(next, depth - 1);
            freeStates.push(next);
            if (solution !== null) {
              break;
            } else {
              results.push(void 0);
            }
          }
          return results;
        }
      }
    };
    phase2search = function(state) {
      var depth, m, ref, results;
      // Initialize phase 2 coordinates
      state.init2();
      results = [];
      for (depth = m = 1, ref = maxDepth - state.depth; (1 <= ref ? m <= ref : m >= ref); depth = 1 <= ref ? ++m : --m) {
        phase2(state, depth);
        if (solution !== null) {
          break;
        }
        results.push(depth++);
      }
      return results;
    };
    phase2 = function(state, depth) {
      var len, m, move, next, ref, results;
      if (depth === 0) {
        if (state.minDist2() === 0) {
          return solution = state.solution();
        }
      } else if (depth > 0) {
        if (state.minDist2() <= depth) {
          ref = state.moves2();
          results = [];
          for (m = 0, len = ref.length; m < len; m++) {
            move = ref[m];
            next = state.next2(move);
            phase2(next, depth - 1);
            freeStates.push(next);
            if (solution !== null) {
              break;
            } else {
              results.push(void 0);
            }
          }
          return results;
        }
      }
    };
    freeStates = (function() {
      var m, ref, results;
      results = [];
      for (x = m = 0, ref = maxDepth + 1; (0 <= ref ? m <= ref : m >= ref); x = 0 <= ref ? ++m : --m) {
        results.push(new State);
      }
      return results;
    })();
    state = freeStates.pop().init(this);
    phase1search(state);
    freeStates.push(state);
    // Trim the trailing space
    if (solution.length > 0) {
      solution = solution.substring(0, solution.length - 1);
    }
    return solution;
  };

  faceNums = {
    U: 0,
    R: 1,
    F: 2,
    D: 3,
    L: 4,
    B: 5
  };

  faceNames = {
    0: 'U',
    1: 'R',
    2: 'F',
    3: 'D',
    4: 'L',
    5: 'B'
  };

  Cube.prototype.solve = function(maxDepth = 22) {
    var clone, len, m, move, ref, rotation, solution, upright, uprightSolution;
    clone = this.clone();
    upright = clone.upright();
    clone.move(upright);
    rotation = new Cube().move(upright).center;
    uprightSolution = clone.solveUpright(maxDepth);
    solution = [];
    ref = uprightSolution.split(' ');
    for (m = 0, len = ref.length; m < len; m++) {
      move = ref[m];
      solution.push(faceNames[rotation[faceNums[move[0]]]]);
      if (move.length > 1) {
        solution[solution.length - 1] += move[1];
      }
    }
    return solution.join(' ');
  };

  Cube.scramble = function() {
    return Cube.inverse(Cube.random().solve());
  };

}).call(this);


var clone = require("lodash/clone");
var map = require("lodash/map");
var assign = require("lodash/assign");
var each = require("lodash/each");
var filter = require("lodash/filter");

var dataBySeries = require("../../util/parse-data-by-series");
var help = require("../../util/helper");
var SessionStore = require("../../stores/SessionStore");

var scaleNames = ["primaryScale", "secondaryScale"];

/**
 * see [ChartConfig#parser](#chartconfig/parser)
 * @see ChartConfig#parser
 * @instance
 * @memberof xy_config
 */
function parseXY(config, _chartProps, callback, parseOpts) {
	// Build chart settings from defaults or provided settings

	parseOpts = parseOpts || {};
	// clone so that we aren't modifying original
	// this can probably be avoided by applying new settings differently
	var chartProps = JSON.parse(JSON.stringify(_chartProps));
	var bySeries = dataBySeries(chartProps.input.raw, {
		checkForDate: true,
		type: chartProps.input.type
	});

	var labels = chartProps._annotations.labels;
	var allColumn = true;
	// check if either scale contains columns, as we'll need to zero the axis
	var _primaryColumn = false;
	var _secondaryColumn = false;
	var _scaleComputed = {};

	each(scaleNames, function(name) {
		_scaleComputed[name] = {
			data: [],
			hasColumn: false,
			count: 0
		};
	});

	var chartSettings = map(bySeries.series, function(dataSeries, i) {
		var settings;

		if (chartProps.chartSettings[i]) {
			settings = chartProps.chartSettings[i];
		} else {
			settings = clone(config.defaultProps.chartProps.chartSettings[0]);
			settings.colorIndex = i;
		}

		if (parseOpts.columnsChanged) {
			settings.label = dataSeries.name;
		} else {
			settings.label = settings.label || dataSeries.name;
		}

		var values = map(dataSeries.values, function(d) {
			return +d.value;
		});

		// add data points to relevant scale
		if (settings.altAxis === false) {

			var _computed = _scaleComputed.primaryScale;
			_computed.data = _computed.data.concat(values);
			_computed.count += 1;
			if (settings.type == "column") {
				_computed.hasColumn = true;
			}

		} else {

			var _computed = _scaleComputed.secondaryScale;
			_computed.data = _computed.data.concat(values);
			_computed.count += 1;
			if (settings.type == "column") {
				_computed.hasColumn = true;
			}

		}

		return settings;

	});

	labels.values = map(bySeries.series, function(dataSeries, i) {
		if (labels.values[i]) {
			return assign({}, { name: chartSettings[i].label}, labels.values[i]);
		} else {
			return { name: dataSeries.name };
		}
	});

	var maxPrecision = 5;
	var factor = Math.pow(10, maxPrecision);

	var scale = {};
	var mobileScale = {};

	// Calculate domain and tick values for any scales that exist
	each(scaleNames, function(name) {
		var _computed = _scaleComputed[name];
		if (_computed.count > 0) {
			var currScale = chartProps.scale[name] || clone(config.defaultProps.chartProps.scale.primaryScale);
			var domain = help.computeScaleDomain(currScale, _computed.data, {
				nice: true,
				minZero: _computed.hasColumn
			});
			assign(currScale, domain);

			var ticks;
			if (name === "primaryScale") {
				ticks = currScale.ticks;
			} else {
				ticks = scale.primaryScale.ticks;
			}

			currScale.tickValues = help.exactTicks(currScale.domain, ticks);
			each(currScale.tickValues, function(v) {
				var tickPrecision = help.precision(Math.round(v*factor)/factor);
				if (tickPrecision > currScale.precision) {
					currScale.precision = tickPrecision;
				}
			});

			scale[name] = currScale;

			if (chartProps.mobile) {
				if (chartProps.mobile.scale) {
					var currMobile = chartProps.mobile.scale[name];
					if (currMobile) {
						var domain = help.computeScaleDomain(currMobile, _computed.data, {
							nice: true,
							minZero: _computed.hasColumn
						});
						assign(currMobile, domain);

						var ticks = (name == "primaryScale") ? currMobile.ticks : scale.primaryScale.ticks;
						currMobile.tickValues = help.exactTicks(currMobile.domain, ticks);
						each(currMobile.tickValues, function(v) {
							var tickPrecision = help.precision(Math.round(v*factor)/factor);
							if (tickPrecision > currMobile.precision) {
								currMobile.precision = tickPrecision;
							}
						});
					}
				}
			} else {
				chartProps.mobile = {};
			}
		}
	});

	// If there is only one primary and >0 secondary, color the left axis
	if (_scaleComputed.primaryScale.count === 1 && _scaleComputed.secondaryScale.count > 0) {
		scale.primaryScale.colorIndex = filter(chartSettings, function(series) {
			return (series.altAxis === false);
		})[0].colorIndex;
	} else {
		scale.primaryScale.colorIndex = null;
	}
	// If there is only one secondary and >0 primary, color the right axis
	if (_scaleComputed.secondaryScale.count === 1 && _scaleComputed.primaryScale.count > 0) {
		scale.secondaryScale.colorIndex = filter(chartSettings, function(series) {
			return (series.altAxis === true);
		})[0].colorIndex;
	} else if (_scaleComputed.secondaryScale.count > 0) {
		scale.secondaryScale.colorIndex = null;
	}

	// create the data structure for the renederer based on input
	if (bySeries.hasDate) {
		scale.hasDate = bySeries.hasDate;
		scale.dateSettings = chartProps.scale.dateSettings || clone(config.defaultProps.chartProps.scale.dateSettings);
		scale.dateSettings.inputTZ = scale.dateSettings.inputTZ || SessionStore.get("nowOffset")
	}

	if (bySeries.isNumeric) {
		scale.isNumeric = bySeries.isNumeric;
		_computed = {
			//TODO look at entries for all series not just the first
			data: bySeries.series[0].values.map(function(d){return +d.entry}),
			hasColumn: false,
			count: 0
		};

		var currScale = chartProps.scale.numericSettings || clone(config.defaultProps.chartProps.scale.numericSettings);
		var domain = help.computeScaleDomain(currScale, _computed.data, {
			nice: true,
			minZero: false
		});

		assign(currScale, domain);

		currScale.ticks = currScale.ticks || help.suggestTickNum(currScale.domain);

		var ticks = currScale.ticks;
		currScale.tickValues = help.exactTicks(currScale.domain, ticks);

		each(currScale.tickValues, function(v) {
			var tickPrecision = help.precision(Math.round(v*factor)/factor);
			if (tickPrecision > currScale.precision) {
				currScale.precision = tickPrecision;
			}
		});

		scale.numericSettings = currScale;

		if (chartProps.mobile) {
			if (chartProps.mobile.scale) {
				var currMobile = chartProps.mobile.scale.numericSettings;
				if (currMobile) {
					var domain = help.computeScaleDomain(currMobile, _computed.data, {
						nice: true,
						minZero: false
					});
					assign(currMobile, domain);

					var ticks = currMobile.ticks;
					currMobile.tickValues = help.exactTicks(currMobile.domain, ticks);
					each(currMobile.tickValues, function(v) {
						var tickPrecision = help.precision(Math.round(v*factor)/factor);
						if (tickPrecision > currMobile.precision) {
							currMobile.precision = tickPrecision;
						}
					});
				}
				chartProps.mobile.scale.numericSettings = currMobile;
			}
		} else {
			chartProps.mobile = {};
		}

	}

	var newChartProps = assign(chartProps, {
		chartSettings: chartSettings,
		scale: scale,
		input: bySeries.input,
		data: bySeries.series,
		_numSecondaryAxis: _scaleComputed.secondaryScale.count
	});

	if (callback) {
		callback(newChartProps);
	} else {
		return newChartProps;
	}

}

module.exports = parseXY;


var clone = require("lodash/clone");
var map = require("lodash/map");
var assign = require("lodash/assign");
var each = require("lodash/each");

var SessionStore = require("../../stores/SessionStore");
var dataBySeries = require("../../util/parse-data-by-series");
var help = require("../../util/helper");

/**
 * see [ChartConfig#parser](#chartconfig/parser)
 * @see ChartConfig#parser
 * @instance
 * @memberof chart_grid_config
 */
function parseChartgrid(config, _chartProps, callback, parseOpts) {
	// Build chart settings from defaults or provided settings
	var chartProps = JSON.parse(JSON.stringify(_chartProps));
	var chartgrid_defaults = config.defaultProps.chartProps;
	var primaryScale = chartProps.scale.primaryScale || clone(config.defaultProps.chartProps.scale.primaryScale);
	var scaleData = [];
	var domain;
	var height;
	var isColumnOrBar;

	parseOpts = parseOpts || {};
	// dont check for date column if grid type is bar
	var checkForDate = chartProps._grid.type !== "bar";
	var bySeries = dataBySeries(chartProps.input.raw, {
			checkForDate: checkForDate,
			type: chartProps.input.type
		});

	var gridSettings = {
		rows: +chartProps._grid.rows || chartgrid_defaults._grid.rows,
		cols: +chartProps._grid.cols || bySeries.series.length
	};

	var chartSettings = map(bySeries.series, function(dataSeries, i) {
		var settings;
		// add data points to relevant scale
		scaleData = scaleData.concat(map(dataSeries.values, function(d) {
			return +d.value;
		}));

		if (chartProps.chartSettings[i]) {
			settings = chartProps.chartSettings[i];
		} else {
			settings = clone(chartgrid_defaults.chartSettings[0]);
			settings.colorIndex = i;
		}

		if (parseOpts.columnsChanged) {
			settings.label = dataSeries.name;
		} else {
			settings.label = settings.label || dataSeries.name;
		}

		return settings;
	});

	chartProps.scale.hasDate = bySeries.hasDate;
	chartProps.scale.isNumeric = bySeries.isNumeric;

	if (bySeries.hasDate) {
		chartProps.scale.dateSettings = chartProps.scale.dateSettings || clone(config.defaultProps.chartProps.scale.dateSettings);
		chartProps.scale.dateSettings.inputTZ = chartProps.scale.dateSettings.inputTZ || SessionStore.get("nowOffset")
		// for dates, default type should be line
		gridSettings.type = _chartProps._grid.type || "line";
		isColumnOrBar = (gridSettings.type === "column" || gridSettings.type === "bar");
		domain = help.computeScaleDomain(primaryScale, scaleData, {
			nice: true,
			minZero: isColumnOrBar
		});
		assign(primaryScale, domain);
	} else if (bySeries.isNumeric) {
		var maxPrecision = 5;
		var factor = Math.pow(10, maxPrecision);
		gridSettings.type = _chartProps._grid.type || "line";

		_computed = {
			//TODO look at entries for all series not just the first
			data: bySeries.series[0].values.map(function(d){return +d.entry}),
			hasColumn: false,
			count: 0
		};

		var currScale = chartProps.scale.numericSettings || clone(config.defaultProps.chartProps.scale.numericSettings);
		var domain = help.computeScaleDomain(currScale, _computed.data, {
			nice: true,
			minZero: false
		});

		assign(currScale, domain);

		var ticks = currScale.ticks;
		currScale.tickValues = help.exactTicks(currScale.domain, ticks);

		each(currScale.tickValues, function(v) {
			var tickPrecision = help.precision(Math.round(v*factor)/factor);
			if (tickPrecision > currScale.precision) {
				currScale.precision = tickPrecision;
			}
		});

		chartProps.scale.numericSettings = currScale;

		if (chartProps.mobile) {
			if (chartProps.mobile.scale) {
				var currMobile = chartProps.mobile.scale.numericSettings;
				if (currMobile) {
					var domain = help.computeScaleDomain(currMobile, _computed.data, {
						nice: true,
						minZero: false
					});
					assign(currMobile, domain);

					var ticks = currMobile.ticks;
					currMobile.tickValues = help.exactTicks(currMobile.domain, ticks);
					each(currMobile.tickValues, function(v) {
						var tickPrecision = help.precision(Math.round(v*factor)/factor);
						if (tickPrecision > currMobile.precision) {
							currMobile.precision = tickPrecision;
						}
					});
				}
				chartProps.mobile.scale.numericSettings = currMobile
			}
		} else {
			chartProps.mobile = {};
		}
	} else {
		// ordinals default type should be bar
		gridSettings.type = _chartProps._grid.type || "bar";
		isColumnOrBar = (gridSettings.type == "column" || gridSettings.type == "bar");
		domain = help.computeScaleDomain(primaryScale, scaleData, {
			minZero: isColumnOrBar
		});
		assign(primaryScale, domain);
	}

	chartProps.scale.primaryScale = primaryScale;
	if (!chartProps.mobile) {
		chartProps.mobile = {};
	}

	if (chartProps.mobile.scale) {
		chartProps.mobile.scale.hasDate = bySeries.hasDate;
	}

	if (gridSettings.type != "bar") {
		// TODO: this function is used in several places and should be factored out
		primaryScale.ticks = primaryScale.ticks || 5;
		primaryScale.precision = primaryScale.precision || 0;
		primaryScale.tickValues = help.exactTicks(primaryScale.domain, primaryScale.ticks);
		each(primaryScale.tickValues, function(v) {
			var tickPrecision = help.precision(Math.round(v*factor)/factor);
			if (tickPrecision > primaryScale.precision) {
				primaryScale.precision = tickPrecision;
			}
		});
	}

	var newChartProps = assign(chartProps, {
		chartSettings: chartSettings,
		scale: chartProps.scale,
		input: bySeries.input,
		_grid: gridSettings,
		data: bySeries.series
	});

	if (callback) {
		callback(newChartProps);
	} else {
		return newChartProps;
	}
}

module.exports = parseChartgrid;


var React = require("react");
var ordinal = require("d3").scale.ordinal;
var map = require("lodash/map");
var assign = require("lodash/assign");
var defaults = require("lodash/defaults");
var range = require("lodash/range");

var gridScaleDefaults = {
	xInnerPadding: 0.1,
	xOuterPadding: 0,
	yInnerPadding: 0.1,
	yOuterPadding: 0
};
/**
 * make_mults
 *
 * @param components { array? of base components that data will be passed to }
 * @param data { data for each chart }
 * @param gridOpts {grid option }
 * @param howToRender { func that tells how to evaluate the components?? }
 * @returns {[chart components]}
 */
function make_mults(Outer, outerProps, data, gridScales, renderDataFunc) {
	var colDomain = gridScales.cols.domain();
	var numCols = colDomain[colDomain.length - 1] + 1;
	// only render number of grid blocks that are selected
	var numCharts = gridScales.cols.domain().length * gridScales.rows.domain().length;

	var grid_dimensions = {
		width: gridScales.cols.rangeBand(),
		height: gridScales.rows.rangeBand()
	};

	return map(data.slice(0, numCharts), function(d, i) {
		var pos = {
			col: i % numCols,
			row: (i === 0) ? 0 : Math.floor( i / numCols )
		};

		var gridProps = assign({}, outerProps, {
			key: i,
			translate: [ gridScales.cols(pos.col), gridScales.rows(pos.row) ],
			dimensions: grid_dimensions
		});

		return Outer(gridProps, renderDataFunc(d, i));
	});
}

function create_grid_scales(gridOpts, outerRange, _opts) {
	var colDomain = range(gridOpts.cols);
	var rowDomain = range(gridOpts.rows);
	var opts = defaults(_opts, gridScaleDefaults);

	return {
		cols: ordinal().domain(colDomain)
					.rangeBands(outerRange.x, opts.xInnerPadding, opts.xOuterPadding),
		rows: ordinal().domain(rowDomain)
					.rangeBands(outerRange.y, opts.yInnerPadding, opts.yOuterPadding)
	}
}

module.exports = {
	makeMults: make_mults,
	createGridScales: create_grid_scales
};


var map = require("lodash/map");
var some = require("lodash/some");
var reduce = require("lodash/reduce");
var sizeof = require("sizeof");

var MAX_BYTES = 240000; // warning for too much data at 240k for chartProps
var INTERVAL_BASE_VALS = [1, 2, 2.5, 5, 10, 25]; // used to determine "good" tick intervals
var MAX_TICKS = 8;

function too_much_data(chartProps) {
	return (sizeof.sizeof(chartProps) > MAX_BYTES);
}

function axis_ticks_even(scale) {
	var range = (scale.domain[1] - scale.domain[0]);
	var minimum = range / MAX_TICKS;
	var digits = Math.floor(range).toString().length;
	var multiplier = Math.pow(10, (digits - 2));

	var acceptable_intervals = reduce(INTERVAL_BASE_VALS, function(prev, curr) {
		var mult = curr * multiplier;

		if (mult >= minimum) {
			prev = prev.concat([mult]);
		}

		return prev;
	}, []);

	var are_ticks_even = some(acceptable_intervals, function(inter) {
		return all_modulo(scale.tickValues, inter);
	});

	return are_ticks_even;
}

function no_prefix_suffix(scale) {
	return (scale.prefix === "" && scale.suffix === "");
}

// Determine if all tickValues are modulo some interval value
function all_modulo(tickValues, interval) {

	// we can't modulo-check decimals so we need to multiply by 10^Ndecimals
	var maxDecimals = reduce(tickValues, function(prevMax, tick) {
		if ((tick % 1) !== 0) {
			return Math.max(prevMax, tick.toString().split(".")[1].length);
		} else {
			return prevMax;
		}
	}, 0);

	var decimalOffset = Math.pow(10, maxDecimals);
	interval = interval * decimalOffset;

	return reduce(tickValues, function(prev, curr) {
		return prev && ((curr * decimalOffset) % interval === 0);
	}, true);

}

module.exports = {
	axisTicksEven: axis_ticks_even,
	tooMuchData: too_much_data,
	noPrefixSuffix: no_prefix_suffix
};


var tabRegex = /[^\t]/gi;
var datePattern = /^(date|time|year)$/i;

function escapeRegExp(str) {
	return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

// Detect delimiter of input
// algorithm: if numtabs >= numrows then TSV, else CSV
function detectDelimiter(input) {
	var numRows = input.split(/\r\n|\r|\n/).length;
	var numTabs = input.replace(tabRegex, "").length;

	if (numTabs >= numRows - 1) {
		return "\t";
	} else {
		return ",";
	}
}

function matchDatePattern(str) {
	return datePattern.test(str);
}

module.exports = {
	escapeRegExp: escapeRegExp,
	detectDelimiter: detectDelimiter,
	matchDatePattern: matchDatePattern
};