(function() {
/**
 * almond 0.2.5 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/jrburke/almond for details
 */
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*jslint sloppy: true */
/*global setTimeout: false */

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

    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,
            foundI, foundStarMap, starI, i, j, part,
            baseParts = baseName && baseName.split("/"),
            map = config.map,
            starMap = (map && map['*']) || {};

        //Adjust any relative paths.
        if (name && name.charAt(0) === ".") {
            //If have a base name, try to normalize against it,
            //otherwise, assume it is a top-level require that will
            //be relative to baseUrl in the end.
            if (baseName) {
                //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.
                baseParts = baseParts.slice(0, baseParts.length - 1);

                name = baseParts.concat(name.split("/"));

                //start trimDots
                for (i = 0; i < name.length; i += 1) {
                    part = name[i];
                    if (part === ".") {
                        name.splice(i, 1);
                        i -= 1;
                    } else if (part === "..") {
                        if (i === 1 && (name[2] === '..' || name[0] === '..')) {
                            //End of the line. Keep at least one non-dot
                            //path segment at the front so it can be mapped
                            //correctly to disk. Otherwise, there is likely
                            //no path mapping for a path starting with '..'.
                            //This can still fail, but catches the most reasonable
                            //uses of ..
                            break;
                        } else if (i > 0) {
                            name.splice(i - 1, 2);
                            i -= 2;
                        }
                    }
                }
                //end trimDots

                name = name.join("/");
            } else if (name.indexOf('./') === 0) {
                // No baseName, so this is ID is resolved relative
                // to baseUrl, pull off the leading dot.
                name = name.substring(2);
            }
        }

        //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
            return req.apply(undef, aps.call(arguments, 0).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];
    }

    /**
     * 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, relName) {
        var plugin,
            parts = splitPrefix(name),
            prefix = parts[0];

        name = parts[1];

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

        //Normalize according
        if (prefix) {
            if (plugin && plugin.normalize) {
                name = plugin.normalize(name, makeNormalize(relName));
            } else {
                name = normalize(name, relName);
            }
        } else {
            name = normalize(name, relName);
            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,
            args = [],
            usingExports;

        //Use name if no relName
        relName = relName || name;

        //Call the callback to define the module, if necessary.
        if (typeof callback === '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], relName);
                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.apply(defined[name], args);

            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, callback).f);
        } else if (!deps.splice) {
            //deps is a config object, not an array.
            config = deps;
            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) {
        config = cfg;
        if (config.deps) {
            req(config.deps, config.callback);
        }
        return req;
    };

    define = function (name, deps, callback) {

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

define("../../vendor/almond/almond", function(){});

// this file is generated during build process by: ./script/generate-js-version.rb
define('lab.version',['require'],function (require) {
  return {
    "repo": {
      "branch": "1.15.1",
      "commit": {
        "sha":           "6cba5c44bff259232acf736609be043573f068be",
        "short_sha":     "6cba5c44",
        "url":           "https://github.com/concord-consortium/lab/commit/6cba5c44",
        "author":        "pjanik",
        "email":         "janikpiotrek@gmail.com",
        "date":          "2017-12-11 18:44:11 +0000",
        "short_message": "Fix DNA Dialog styles",
        "message":       "Fix DNA Dialog styles\n\n[#151683925]"
      },
      "last_tag":        "1.15.1",
      "dirty": false
    }
  };
});

define('lab.config',[],function () {
  return {
    "sharing": true,
    "logging": false,
    "tracing": false,
    // Set homeForSharing to the host where shared Interactives are found
    // if you don't want to share the ones on the actual server.
    // Example if you host the Interactives on a static S3 site and want the
    // sharing links to point to the same Interactives at http://lab.concord.org
    "homeForSharing": "",
    "homeEmbeddablePath": "/embeddable.html",
    // Root URL of Lab distribution, used to get Lab resources (e.g. DNA images).
    "rootUrl": "lab",
    // Models root URL, appended to all model paths. Leave it empty if model paths are relative
    // to page that contains Lab interactive.
    "modelsRootUrl": "",
    // Set codap to true if Lab is running inside of CODAP
    "codap": false,
    // dataGamesProxyPrefix was the old way of configuring CODAP
    "dataGamesProxyPrefix": "",
    "utmCampaign": null,
    // You can set versioned home to function that accepts major version of Lab and returns
    // URL of embeddable page that uses particular version of Lab, e.g.:
    // Lab.config.versionedHome = function (version) {
    //    return "http://some.domain.com/lab/embeddable-" + version + ".html";
    // }
    // When Lab receives 'getLearnerUrl' messaga via iframe phone, it will respond providing
    // return value of this function.
    "versionedHome": null
  };
});

// seedrandom.js version 2.2.
// Author: David Bau
// Date: 2013 Jun 15
//
// Defines a method Math.seedrandom() that, when called, substitutes
// an explicitly seeded RC4-based algorithm for Math.random().  Also
// supports automatic seeding from local or network sources of entropy.
//
// http://davidbau.com/encode/seedrandom.js
// http://davidbau.com/encode/seedrandom-min.js
//
// Usage:
//
//   <script src=http://davidbau.com/encode/seedrandom-min.js></script>
//
//   Math.seedrandom('yay.');  Sets Math.random to a function that is
//                             initialized using the given explicit seed.
//
//   Math.seedrandom();        Sets Math.random to a function that is
//                             seeded using the current time, dom state,
//                             and other accumulated local entropy.
//                             The generated seed string is returned.
//
//   Math.seedrandom('yowza.', true);
//                             Seeds using the given explicit seed mixed
//                             together with accumulated entropy.
//
//   <script src="https://jsonlib.appspot.com/urandom?callback=Math.seedrandom">
//   </script>                 Seeds using urandom bits from a server.
//
// More advanced examples:
//
//   Math.seedrandom("hello.");           // Use "hello." as the seed.
//   document.write(Math.random());       // Always 0.9282578795792454
//   document.write(Math.random());       // Always 0.3752569768646784
//   var rng1 = Math.random;              // Remember the current prng.
//
//   var autoseed = Math.seedrandom();    // New prng with an automatic seed.
//   document.write(Math.random());       // Pretty much unpredictable x.
//
//   Math.random = rng1;                  // Continue "hello." prng sequence.
//   document.write(Math.random());       // Always 0.7316977468919549
//
//   Math.seedrandom(autoseed);           // Restart at the previous seed.
//   document.write(Math.random());       // Repeat the 'unpredictable' x.
//
//   function reseed(event, count) {      // Define a custom entropy collector.
//     var t = [];
//     function w(e) {
//       t.push([e.pageX, e.pageY, +new Date]);
//       if (t.length < count) { return; }
//       document.removeEventListener(event, w);
//       Math.seedrandom(t, true);        // Mix in any previous entropy.
//     }
//     document.addEventListener(event, w);
//   }
//   reseed('mousemove', 100);            // Reseed after 100 mouse moves.
//
// Version notes:
//
// The random number sequence is the same as version 1.0 for string seeds.
// Version 2.0 changed the sequence for non-string seeds.
// Version 2.1 speeds seeding and uses window.crypto to autoseed if present.
// Version 2.2 alters non-crypto autoseeding to sweep up entropy from plugins.
//
// The standard ARC4 key scheduler cycles short keys, which means that
// seedrandom('ab') is equivalent to seedrandom('abab') and 'ababab'.
// Therefore it is a good idea to add a terminator to avoid trivial
// equivalences on short string seeds, e.g., Math.seedrandom(str + '\0').
// Starting with version 2.0, a terminator is added automatically for
// non-string seeds, so seeding with the number 111 is the same as seeding
// with '111\0'.
//
// When seedrandom() is called with zero args, it uses a seed
// drawn from the browser crypto object if present.  If there is no
// crypto support, seedrandom() uses the current time, the native rng,
// and a walk of several DOM objects to collect a few bits of entropy.
//
// Each time the one- or two-argument forms of seedrandom are called,
// entropy from the passed seed is accumulated in a pool to help generate
// future seeds for the zero- and two-argument forms of seedrandom.
//
// On speed - This javascript implementation of Math.random() is about
// 3-10x slower than the built-in Math.random() because it is not native
// code, but that is typically fast enough.  Some details (timings on
// Chrome 25 on a 2010 vintage macbook):
//
// seeded Math.random()          - avg less than 0.0002 milliseconds per call
// seedrandom('explicit.')       - avg less than 0.2 milliseconds per call
// seedrandom('explicit.', true) - avg less than 0.2 milliseconds per call
// seedrandom() with crypto      - avg less than 0.2 milliseconds per call
//
// Autoseeding without crypto is somewhat slower, about 20-30 milliseconds on
// a 2012 windows 7 1.5ghz i5 laptop, as seen on Firefox 19, IE 10, and Opera.
// Seeded rng calls themselves are fast across these browsers, with slowest
// numbers on Opera at about 0.0005 ms per seeded Math.random().
//
// LICENSE (BSD):
//
// Copyright 2013 David Bau, 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. Neither the name of this module 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.
//
/**
 * All code is in an anonymous closure to keep the global namespace clean.
 */
(function (
    global, pool, math, width, chunks, digits) {

//
// The following constants are related to IEEE 754 limits.
//
var startdenom = math.pow(width, chunks),
    significance = math.pow(2, digits),
    overflow = significance * 2,
    mask = width - 1;

//
// seedrandom()
// This is the seedrandom function described above.
//
math['seedrandom'] = function(seed, use_entropy) {
  var key = [];

  // Flatten the seed string or build one from local entropy if needed.
  var shortseed = mixkey(flatten(
    use_entropy ? [seed, tostring(pool)] :
    0 in arguments ? seed : autoseed(), 3), key);

  // Use the seed to initialize an ARC4 generator.
  var arc4 = new ARC4(key);

  // Mix the randomness into accumulated entropy.
  mixkey(tostring(arc4.S), pool);

  // Override Math.random

  // This function returns a random double in [0, 1) that contains
  // randomness in every bit of the mantissa of the IEEE 754 value.

  math['random'] = function() {         // Closure to return a random double:
    var n = arc4.g(chunks),             // Start with a numerator n < 2 ^ 48
        d = startdenom,                 //   and denominator d = 2 ^ 48.
        x = 0;                          //   and no 'extra last byte'.
    while (n < significance) {          // Fill up all significant digits by
      n = (n + x) * width;              //   shifting numerator and
      d *= width;                       //   denominator and generating a
      x = arc4.g(1);                    //   new least-significant-byte.
    }
    while (n >= overflow) {             // To avoid rounding up, before adding
      n /= 2;                           //   last byte, shift everything
      d /= 2;                           //   right using integer math until
      x >>>= 1;                         //   we have exactly the desired bits.
    }
    return (n + x) / d;                 // Form the number within [0, 1).
  };

  // Return the seed that was used
  return shortseed;
};

//
// ARC4
//
// An ARC4 implementation.  The constructor takes a key in the form of
// an array of at most (width) integers that should be 0 <= x < (width).
//
// The g(count) method returns a pseudorandom integer that concatenates
// the next (count) outputs from ARC4.  Its return value is a number x
// that is in the range 0 <= x < (width ^ count).
//
/** @constructor */
function ARC4(key) {
  var t, keylen = key.length,
      me = this, i = 0, j = me.i = me.j = 0, s = me.S = [];

  // The empty key [] is treated as [0].
  if (!keylen) { key = [keylen++]; }

  // Set up S using the standard key scheduling algorithm.
  while (i < width) {
    s[i] = i++;
  }
  for (i = 0; i < width; i++) {
    s[i] = s[j = mask & (j + key[i % keylen] + (t = s[i]))];
    s[j] = t;
  }

  // The "g" method returns the next (count) outputs as one number.
  (me.g = function(count) {
    // Using instance members instead of closure state nearly doubles speed.
    var t, r = 0,
        i = me.i, j = me.j, s = me.S;
    while (count--) {
      t = s[i = mask & (i + 1)];
      r = r * width + s[mask & ((s[i] = s[j = mask & (j + t)]) + (s[j] = t))];
    }
    me.i = i; me.j = j;
    return r;
    // For robust unpredictability discard an initial batch of values.
    // See http://www.rsa.com/rsalabs/node.asp?id=2009
  })(width);
}

//
// flatten()
// Converts an object tree to nested arrays of strings.
//
function flatten(obj, depth) {
  var result = [], typ = (typeof obj)[0], prop;
  if (depth && typ == 'o') {
    for (prop in obj) {
      try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {}
    }
  }
  return (result.length ? result : typ == 's' ? obj : obj + '\0');
}

//
// mixkey()
// Mixes a string seed into a key that is an array of integers, and
// returns a shortened string seed that is equivalent to the result key.
//
function mixkey(seed, key) {
  var stringseed = seed + '', smear, j = 0;
  while (j < stringseed.length) {
    key[mask & j] =
      mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++));
  }
  return tostring(key);
}

//
// autoseed()
// Returns an object for autoseeding, using window.crypto if available.
//
/** @param {Uint8Array=} seed */
function autoseed(seed) {
  try {
    global.crypto.getRandomValues(seed = new Uint8Array(width));
    return tostring(seed);
  } catch (e) {
    return [+new Date, global, global.navigator.plugins,
            global.screen, tostring(pool)];
  }
}

//
// tostring()
// Converts an array of charcodes to a string
//
function tostring(a) {
  return String.fromCharCode.apply(0, a);
}

//
// When seedrandom.js is loaded, we immediately mix a few bits
// from the built-in RNG into the entropy pool.  Because we do
// not want to intefere with determinstic PRNG state later,
// seedrandom will not call math.random on its own again after
// initialization.
//
mixkey(math.random(), pool);

// End anonymous scope, and pass initial values.
})(
  this,   // global window object
  [],     // pool: entropy pool starts empty
  Math,   // math: package containing random, pow, and seedrandom
  256,    // width: each RC4 output is 0 <= x < 256
  6,      // chunks: at least six RC4 outputs for each double
  52      // digits: there are 52 significant digits in a double
);

define("seedrandom", function(){});

/*global window Uint8Array Uint8ClampedArray Int8Array Uint16Array Int16Array Uint32Array Int32Array Float32Array Float64Array */
/*jshint newcap: false */

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. R.JS Optimizer will strip out this if statement.


define('arrays',['require','exports','module'],function (require, exports, module) {
  var arrays = {};

  arrays.version = '0.0.1';

  arrays.webgl = (typeof window !== 'undefined') && !!window.WebGLRenderingContext;

  arrays.typed = (function() {
    try {
      new Float64Array(0);
      return true;
    } catch(e) {
      return false;
    }
  }());

  // http://www.khronos.org/registry/typedarray/specs/latest/#TYPEDARRAYS
  // regular
  // Uint8Array
  // Uint8ClampedArray
  // Uint16Array
  // Uint32Array
  // Int8Array
  // Int16Array
  // Int32Array
  // Float32Array
  // Float64Array

  arrays.create = function(size, fill, array_type) {
    if (!array_type) {
      if (arrays.webgl || arrays.typed) {
        array_type = "Float32Array";
      } else {
        array_type = "regular";
      }
    }
    if (fill === undefined) {
      fill = 0;
    }
    var a, i;
    if (array_type === "regular") {
      a = new Array(size);
    } else {
      switch(array_type) {
        case "Float64Array":
          a = new Float64Array(size);
          break;
        case "Float32Array":
          a = new Float32Array(size);
          break;
        case "Int32Array":
          a = new Int32Array(size);
          break;
        case "Int16Array":
          a = new Int16Array(size);
          break;
        case "Int8Array":
          a = new Int8Array(size);
          break;
        case "Uint32Array":
          a = new Uint32Array(size);
          break;
        case "Uint16Array":
          a = new Uint16Array(size);
          break;
        case "Uint8Array":
          a = new Uint8Array(size);
          break;
        case "Uint8ClampedArray":
          a = new Uint8ClampedArray(size);
          break;
        default:
          throw new Error("arrays: couldn't understand array type \"" + array_type + "\".");
      }
    }
    arrays.fill(a, fill);
    return a;
  };

  arrays.fill = function(array, value) {
    var i = -1, size = array.length;
    while(++i < size) {
      array[i] = value;
    }
  };

  arrays.constructor_function = function(source) {
    if (source.buffer &&
        source.buffer.__proto__ &&
        source.buffer.__proto__.constructor &&
        Object.prototype.toString.call(source) === "[object Array]") {
      return source.__proto__.constructor;
    }

    switch(source.constructor) {
      case Array:             return Array;
      case Float32Array:      return Float32Array;
      case Uint8Array:        return Uint8Array;
      case Float64Array:      return Float64Array;
      case Int32Array:        return Int32Array;
      case Int16Array:        return Int16Array;
      case Int8Array:         return Int8Array;
      case Uint32Array:       return Uint32Array;
      case Uint16Array:       return Uint16Array;
      case Uint8ClampedArray: return Uint8ClampedArray;
      default:
        throw new Error(
            "arrays.constructor_function: must be an Array or Typed Array: " + "  source: " + source);
            // ", source.constructor: " + source.constructor +
            // ", source.buffer: " + source.buffer +
            // ", source.buffer.slice: " + source.buffer.slice +
            // ", source.buffer.__proto__: " + source.buffer.__proto__ +
            // ", source.buffer.__proto__.constructor: " + source.buffer.__proto__.constructor
      }
  };

  arrays.copy = function(source, dest, num) {
    var len = num !== undefined ? num : source.length,
        i = -1;
    while(++i < len) { dest[i] = source[i]; }
    if (arrays.constructor_function(dest) === Array) dest.length = len;
    return dest;
  };

  arrays.clone = function(source) {
    var i, len = source.length, clone, constructor;
    constructor = arrays.constructor_function(source);
    if (constructor === Array) {
      clone = new constructor(len);
      for (i = 0; i < len; i++) { clone[i] = source[i]; }
      return clone;
    }
    if (source.buffer.slice) {
      clone = new constructor(source.buffer.slice(0));
      return clone;
    }
    clone = new constructor(len);
    for (i = 0; i < len; i++) { clone[i] = source[i]; }
    return clone;
  };

  /** @return true if x is between a and b. */
  // float a, float b, float x
  arrays.between = function(a, b, x) {
    return x < Math.max(a, b) && x > Math.min(a, b);
  };

  // float[] array
  arrays.max = function(array) {
    return Math.max.apply( Math, array );
  };

  // float[] array
  arrays.min = function(array) {
    return Math.min.apply( Math, array );
  };

  // FloatxxArray[] array
  arrays.maxTypedArray = function(array) {
    var test, i,
    max = Number.MIN_VALUE,
    length = array.length;
    for(i = 0; i < length; i++) {
      test = array[i];
      max = test > max ? test : max;
    }
    return max;
  };

  // FloatxxArray[] array
  arrays.minTypedArray = function(array) {
    var test, i,
    min = Number.MAX_VALUE,
    length = array.length;
    for(i = 0; i < length; i++) {
      test = array[i];
      min = test < min ? test : min;
    }
    return min;
  };

  // float[] array
  arrays.maxAnyArray = function(array) {
    try {
      return Math.max.apply( Math, array );
    }
    catch (e) {
      if (e instanceof TypeError) {
        var test, i,
        max = Number.MIN_VALUE,
        length = array.length;
        for(i = 0; i < length; i++) {
          test = array[i];
          max = test > max ? test : max;
        }
        return max;
      }
    }
  };

  // float[] array
  arrays.minAnyArray = function(array) {
    try {
      return Math.min.apply( Math, array );
    }
    catch (e) {
      if (e instanceof TypeError) {
        var test, i,
        min = Number.MAX_VALUE,
        length = array.length;
        for(i = 0; i < length; i++) {
          test = array[i];
          min = test < min ? test : min;
        }
        return min;
      }
    }
  };

  arrays.average = function(array) {
    var i, acc = 0,
    length = array.length;
    for (i = 0; i < length; i++) {
      acc += array[i];
    }
    return acc / length;
  };

  /**
    Create a new array of the same type as 'array' and of length 'newLength', and copies as many
    elements from 'array' to the new array as is possible.

    If 'newLength' is less than 'array.length', and 'array' is  a typed array, we still allocate a
    new, shorter array in order to allow GC to work.

    The returned array should always take the place of the passed-in 'array' in client code, and this
    method should not be counted on to always return a copy. If 'array' is non-typed, we manipulate
    its length instead of copying it. But if 'array' is typed, we cannot increase its size in-place,
    therefore must pas a *new* object reference back to client code.
  */
  arrays.extend = function(array, newLength) {
    var extendedArray,
        Constructor,
        i;

    Constructor = arrays.constructor_function(array);

    if (Constructor === Array) {
      i = array.length;
      array.length = newLength;
      // replicate behavior of typed-arrays by filling with 0
      for(;i < newLength; i++) { array[i] = 0; }
      return array;
    }

    extendedArray = new Constructor(newLength);

    // prevent 'set' method from erroring when array.length > newLength, by using the (no-copy) method
    // 'subarray' to get an array view that is clamped to length = min(array.length, newLength)
    extendedArray.set(array.subarray(0, newLength));

    return extendedArray;
  };

  arrays.remove = function(array, idx) {
    var constructor = arrays.constructor_function(array),
        rest;

    if (constructor !== Array) {
      throw new Error("arrays.remove for typed arrays not implemented yet.");
    }

    rest = array.slice(idx + 1);
    array.length = idx;
    Array.prototype.push.apply(array, rest);

    return array;
  };

  arrays.isArray = function (object) {
    if (object === undefined || object === null) {
      return false;
    }
    switch(Object.prototype.toString.call(object)) {
      case "[object Array]":
      case "[object Float32Array]":
      case "[object Float64Array]":
      case "[object Uint8Array]":
      case "[object Uint16Array]":
      case "[object Uint32Array]":
      case "[object Uint8ClampedArray]":
      case "[object Int8Array]":
      case "[object Int16Array]":
      case "[object Int32Array]":
        return true;
      default:
        return false;
    }
  };

  // publish everything to exports
  for (var key in arrays) {
    if (arrays.hasOwnProperty(key)) exports[key] = arrays[key];
  }
});

/**
 * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
 *
 * @version 0.6.11
 * @codingstandard ftlabs-jsv2
 * @copyright The Financial Times Limited [All Rights Reserved]
 * @license MIT License (see LICENSE.txt)
 */

/*jslint browser:true, node:true*/
/*global define, Event, Node*/


/**
 * Instantiate fast-clicking listeners on the specificed layer.
 *
 * @constructor
 * @param {Element} layer The layer to listen on
 */
function FastClick(layer) {
	'use strict';
	var oldOnClick, self = this;


	/**
	 * Whether a click is currently being tracked.
	 *
	 * @type boolean
	 */
	this.trackingClick = false;


	/**
	 * Timestamp for when when click tracking started.
	 *
	 * @type number
	 */
	this.trackingClickStart = 0;


	/**
	 * The element being tracked for a click.
	 *
	 * @type EventTarget
	 */
	this.targetElement = null;


	/**
	 * X-coordinate of touch start event.
	 *
	 * @type number
	 */
	this.touchStartX = 0;


	/**
	 * Y-coordinate of touch start event.
	 *
	 * @type number
	 */
	this.touchStartY = 0;


	/**
	 * ID of the last touch, retrieved from Touch.identifier.
	 *
	 * @type number
	 */
	this.lastTouchIdentifier = 0;


	/**
	 * Touchmove boundary, beyond which a click will be cancelled.
	 *
	 * @type number
	 */
	this.touchBoundary = 10;


	/**
	 * The FastClick layer.
	 *
	 * @type Element
	 */
	this.layer = layer;

	if (!layer || !layer.nodeType) {
		throw new TypeError('Layer must be a document node');
	}

	/** @type function() */
	this.onClick = function() { return FastClick.prototype.onClick.apply(self, arguments); };

	/** @type function() */
	this.onMouse = function() { return FastClick.prototype.onMouse.apply(self, arguments); };

	/** @type function() */
	this.onTouchStart = function() { return FastClick.prototype.onTouchStart.apply(self, arguments); };

	/** @type function() */
	this.onTouchMove = function() { return FastClick.prototype.onTouchMove.apply(self, arguments); };

	/** @type function() */
	this.onTouchEnd = function() { return FastClick.prototype.onTouchEnd.apply(self, arguments); };

	/** @type function() */
	this.onTouchCancel = function() { return FastClick.prototype.onTouchCancel.apply(self, arguments); };

	if (FastClick.notNeeded(layer)) {
		return;
	}

	// Set up event handlers as required
	if (this.deviceIsAndroid) {
		layer.addEventListener('mouseover', this.onMouse, true);
		layer.addEventListener('mousedown', this.onMouse, true);
		layer.addEventListener('mouseup', this.onMouse, true);
	}

	layer.addEventListener('click', this.onClick, true);
	layer.addEventListener('touchstart', this.onTouchStart, false);
	layer.addEventListener('touchmove', this.onTouchMove, false);
	layer.addEventListener('touchend', this.onTouchEnd, false);
	layer.addEventListener('touchcancel', this.onTouchCancel, false);

	// Hack is required for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
	// which is how FastClick normally stops click events bubbling to callbacks registered on the FastClick
	// layer when they are cancelled.
	if (!Event.prototype.stopImmediatePropagation) {
		layer.removeEventListener = function(type, callback, capture) {
			var rmv = Node.prototype.removeEventListener;
			if (type === 'click') {
				rmv.call(layer, type, callback.hijacked || callback, capture);
			} else {
				rmv.call(layer, type, callback, capture);
			}
		};

		layer.addEventListener = function(type, callback, capture) {
			var adv = Node.prototype.addEventListener;
			if (type === 'click') {
				adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
					if (!event.propagationStopped) {
						callback(event);
					}
				}), capture);
			} else {
				adv.call(layer, type, callback, capture);
			}
		};
	}

	// If a handler is already declared in the element's onclick attribute, it will be fired before
	// FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
	// adding it as listener.
	if (typeof layer.onclick === 'function') {

		// Android browser on at least 3.2 requires a new reference to the function in layer.onclick
		// - the old one won't work if passed to addEventListener directly.
		oldOnClick = layer.onclick;
		layer.addEventListener('click', function(event) {
			oldOnClick(event);
		}, false);
		layer.onclick = null;
	}
}


/**
 * Android requires exceptions.
 *
 * @type boolean
 */
FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;


/**
 * iOS requires exceptions.
 *
 * @type boolean
 */
FastClick.prototype.deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent);


/**
 * iOS 4 requires an exception for select elements.
 *
 * @type boolean
 */
FastClick.prototype.deviceIsIOS4 = FastClick.prototype.deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent);


/**
 * iOS 6.0(+?) requires the target element to be manually derived
 *
 * @type boolean
 */
FastClick.prototype.deviceIsIOSWithBadTarget = FastClick.prototype.deviceIsIOS && (/OS ([6-9]|\d{2})_\d/).test(navigator.userAgent);


/**
 * Determine whether a given element requires a native click.
 *
 * @param {EventTarget|Element} target Target DOM element
 * @returns {boolean} Returns true if the element needs a native click
 */
FastClick.prototype.needsClick = function(target) {
	'use strict';
	switch (target.nodeName.toLowerCase()) {

	// Don't send a synthetic click to disabled inputs (issue #62)
	case 'button':
	case 'select':
	case 'textarea':
		if (target.disabled) {
			return true;
		}

		break;
	case 'input':

		// File inputs need real clicks on iOS 6 due to a browser bug (issue #68)
		if ((this.deviceIsIOS && target.type === 'file') || target.disabled) {
			return true;
		}

		break;
	case 'label':
	case 'video':
		return true;
	}

	return (/\bneedsclick\b/).test(target.className);
};


/**
 * Determine whether a given element requires a call to focus to simulate click into element.
 *
 * @param {EventTarget|Element} target Target DOM element
 * @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
 */
FastClick.prototype.needsFocus = function(target) {
	'use strict';
	switch (target.nodeName.toLowerCase()) {
	case 'textarea':
		return true;
	case 'select':
		return !this.deviceIsAndroid;
	case 'input':
		switch (target.type) {
		case 'button':
		case 'checkbox':
		case 'file':
		case 'image':
		case 'radio':
		case 'submit':
			return false;
		}

		// No point in attempting to focus disabled inputs
		return !target.disabled && !target.readOnly;
	default:
		return (/\bneedsfocus\b/).test(target.className);
	}
};


/**
 * Send a click event to the specified element.
 *
 * @param {EventTarget|Element} targetElement
 * @param {Event} event
 */
FastClick.prototype.sendClick = function(targetElement, event) {
	'use strict';
	var clickEvent, touch;

	// On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
	if (document.activeElement && document.activeElement !== targetElement) {
		document.activeElement.blur();
	}

	touch = event.changedTouches[0];

	// Synthesise a click event, with an extra attribute so it can be tracked
	clickEvent = document.createEvent('MouseEvents');
	clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
	clickEvent.forwardedTouchEvent = true;
	targetElement.dispatchEvent(clickEvent);
};

FastClick.prototype.determineEventType = function(targetElement) {
	'use strict;'

	//Issue #159: Android Chrome Select Box does not open with a synthetic click event
	if (this.deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') {
		return 'mousedown';
	}

	return 'click';
}


/**
 * @param {EventTarget|Element} targetElement
 */
FastClick.prototype.focus = function(targetElement) {
	'use strict';
	var length;

	// Issue #160: on iOS 7, some input elements (e.g. date datetime) throw a vague TypeError on setSelectionRange. These elements don't have an integer value for the selectionStart and selectionEnd properties, but unfortunately that can't be used for detection because accessing the properties also throws a TypeError. Just check the type instead. Filed as Apple bug #15122724.
	if (this.deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time') {
		length = targetElement.value.length;
		targetElement.setSelectionRange(length, length);
	} else {
		targetElement.focus();
	}
};


/**
 * Check whether the given target element is a child of a scrollable layer and if so, set a flag on it.
 *
 * @param {EventTarget|Element} targetElement
 */
FastClick.prototype.updateScrollParent = function(targetElement) {
	'use strict';
	var scrollParent, parentElement;

	scrollParent = targetElement.fastClickScrollParent;

	// Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the
	// target element was moved to another parent.
	if (!scrollParent || !scrollParent.contains(targetElement)) {
		parentElement = targetElement;
		do {
			if (parentElement.scrollHeight > parentElement.offsetHeight) {
				scrollParent = parentElement;
				targetElement.fastClickScrollParent = parentElement;
				break;
			}

			parentElement = parentElement.parentElement;
		} while (parentElement);
	}

	// Always update the scroll top tracker if possible.
	if (scrollParent) {
		scrollParent.fastClickLastScrollTop = scrollParent.scrollTop;
	}
};


/**
 * @param {EventTarget} targetElement
 * @returns {Element|EventTarget}
 */
FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) {
	'use strict';

	// On some older browsers (notably Safari on iOS 4.1 - see issue #56) the event target may be a text node.
	if (eventTarget.nodeType === Node.TEXT_NODE) {
		return eventTarget.parentNode;
	}

	return eventTarget;
};


/**
 * On touch start, record the position and scroll offset.
 *
 * @param {Event} event
 * @returns {boolean}
 */
FastClick.prototype.onTouchStart = function(event) {
	'use strict';
	var targetElement, touch, selection;

	// Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111).
	if (event.targetTouches.length > 1) {
		return true;
	}

	targetElement = this.getTargetElementFromEventTarget(event.target);
	touch = event.targetTouches[0];

	if (this.deviceIsIOS) {

		// Only trusted events will deselect text on iOS (issue #49)
		selection = window.getSelection();
		if (selection.rangeCount && !selection.isCollapsed) {
			return true;
		}

		if (!this.deviceIsIOS4) {

			// Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23):
			// when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
			// with the same identifier as the touch event that previously triggered the click that triggered the alert.
			// Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an
			// immediately preceeding touch event (issue #52), so this fix is unavailable on that platform.
			if (touch.identifier === this.lastTouchIdentifier) {
				event.preventDefault();
				return false;
			}

			this.lastTouchIdentifier = touch.identifier;

			// If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
			// 1) the user does a fling scroll on the scrollable layer
			// 2) the user stops the fling scroll with another tap
			// then the event.target of the last 'touchend' event will be the element that was under the user's finger
			// when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
			// is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
			this.updateScrollParent(targetElement);
		}
	}

	this.trackingClick = true;
	this.trackingClickStart = event.timeStamp;
	this.targetElement = targetElement;

	this.touchStartX = touch.pageX;
	this.touchStartY = touch.pageY;

	// Prevent phantom clicks on fast double-tap (issue #36)
	if ((event.timeStamp - this.lastClickTime) < 200) {
		event.preventDefault();
	}

	return true;
};


/**
 * Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
 *
 * @param {Event} event
 * @returns {boolean}
 */
FastClick.prototype.touchHasMoved = function(event) {
	'use strict';
	var touch = event.changedTouches[0], boundary = this.touchBoundary;

	if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) {
		return true;
	}

	return false;
};


/**
 * Update the last position.
 *
 * @param {Event} event
 * @returns {boolean}
 */
FastClick.prototype.onTouchMove = function(event) {
	'use strict';
	if (!this.trackingClick) {
		return true;
	}

	// If the touch has moved, cancel the click tracking
	if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
		this.trackingClick = false;
		this.targetElement = null;
	}

	return true;
};


/**
 * Attempt to find the labelled control for the given label element.
 *
 * @param {EventTarget|HTMLLabelElement} labelElement
 * @returns {Element|null}
 */
FastClick.prototype.findControl = function(labelElement) {
	'use strict';

	// Fast path for newer browsers supporting the HTML5 control attribute
	if (labelElement.control !== undefined) {
		return labelElement.control;
	}

	// All browsers under test that support touch events also support the HTML5 htmlFor attribute
	if (labelElement.htmlFor) {
		return document.getElementById(labelElement.htmlFor);
	}

	// If no for attribute exists, attempt to retrieve the first labellable descendant element
	// the list of which is defined here: http://www.w3.org/TR/html5/forms.html#category-label
	return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
};


/**
 * On touch end, determine whether to send a click event at once.
 *
 * @param {Event} event
 * @returns {boolean}
 */
FastClick.prototype.onTouchEnd = function(event) {
	'use strict';
	var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;

	if (!this.trackingClick) {
		return true;
	}

	// Prevent phantom clicks on fast double-tap (issue #36)
	if ((event.timeStamp - this.lastClickTime) < 200) {
		this.cancelNextClick = true;
		return true;
	}

	// Reset to prevent wrong click cancel on input (issue #156).
	this.cancelNextClick = false;

	this.lastClickTime = event.timeStamp;

	trackingClickStart = this.trackingClickStart;
	this.trackingClick = false;
	this.trackingClickStart = 0;

	// On some iOS devices, the targetElement supplied with the event is invalid if the layer
	// is performing a transition or scroll, and has to be re-detected manually. Note that
	// for this to function correctly, it must be called *after* the event target is checked!
	// See issue #57; also filed as rdar://13048589 .
	if (this.deviceIsIOSWithBadTarget) {
		touch = event.changedTouches[0];

		// In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null
		targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
		targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
	}

	targetTagName = targetElement.tagName.toLowerCase();
	if (targetTagName === 'label') {
		forElement = this.findControl(targetElement);
		if (forElement) {
			this.focus(targetElement);
			if (this.deviceIsAndroid) {
				return false;
			}

			targetElement = forElement;
		}
	} else if (this.needsFocus(targetElement)) {

		// Case 1: If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through.
		// Case 2: Without this exception for input elements tapped when the document is contained in an iframe, then any inputted text won't be visible even though the value attribute is updated as the user types (issue #37).
		if ((event.timeStamp - trackingClickStart) > 100 || (this.deviceIsIOS && window.top !== window && targetTagName === 'input')) {
			this.targetElement = null;
			return false;
		}

		this.focus(targetElement);

		// Select elements need the event to go through on iOS 4, otherwise the selector menu won't open.
		if (!this.deviceIsIOS4 || targetTagName !== 'select') {
			this.targetElement = null;
			event.preventDefault();
		}

		return false;
	}

	if (this.deviceIsIOS && !this.deviceIsIOS4) {

		// Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled
		// and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).
		scrollParent = targetElement.fastClickScrollParent;
		if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
			return true;
		}
	}

	// Prevent the actual click from going though - unless the target node is marked as requiring
	// real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted.
	if (!this.needsClick(targetElement)) {
		event.preventDefault();
		this.sendClick(targetElement, event);
	}

	return false;
};


/**
 * On touch cancel, stop tracking the click.
 *
 * @returns {void}
 */
FastClick.prototype.onTouchCancel = function() {
	'use strict';
	this.trackingClick = false;
	this.targetElement = null;
};


/**
 * Determine mouse events which should be permitted.
 *
 * @param {Event} event
 * @returns {boolean}
 */
FastClick.prototype.onMouse = function(event) {
	'use strict';

	// If a target element was never set (because a touch event was never fired) allow the event
	if (!this.targetElement) {
		return true;
	}

	if (event.forwardedTouchEvent) {
		return true;
	}

	// Programmatically generated events targeting a specific element should be permitted
	if (!event.cancelable) {
		return true;
	}

	// Derive and check the target element to see whether the mouse event needs to be permitted;
	// unless explicitly enabled, prevent non-touch click events from triggering actions,
	// to prevent ghost/doubleclicks.
	if (!this.needsClick(this.targetElement) || this.cancelNextClick) {

		// Prevent any user-added listeners declared on FastClick element from being fired.
		if (event.stopImmediatePropagation) {
			event.stopImmediatePropagation();
		} else {

			// Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
			event.propagationStopped = true;
		}

		// Cancel the event
		event.stopPropagation();
		event.preventDefault();

		return false;
	}

	// If the mouse event is permitted, return true for the action to go through.
	return true;
};


/**
 * On actual clicks, determine whether this is a touch-generated click, a click action occurring
 * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
 * an actual click which should be permitted.
 *
 * @param {Event} event
 * @returns {boolean}
 */
FastClick.prototype.onClick = function(event) {
	'use strict';
	var permitted;

	// It's possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early.
	if (this.trackingClick) {
		this.targetElement = null;
		this.trackingClick = false;
		return true;
	}

	// Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target.
	if (event.target.type === 'submit' && event.detail === 0) {
		return true;
	}

	permitted = this.onMouse(event);

	// Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser's click doesn't go through.
	if (!permitted) {
		this.targetElement = null;
	}

	// If clicks are permitted, return true for the action to go through.
	return permitted;
};


/**
 * Remove all FastClick's event listeners.
 *
 * @returns {void}
 */
FastClick.prototype.destroy = function() {
	'use strict';
	var layer = this.layer;

	if (this.deviceIsAndroid) {
		layer.removeEventListener('mouseover', this.onMouse, true);
		layer.removeEventListener('mousedown', this.onMouse, true);
		layer.removeEventListener('mouseup', this.onMouse, true);
	}

	layer.removeEventListener('click', this.onClick, true);
	layer.removeEventListener('touchstart', this.onTouchStart, false);
	layer.removeEventListener('touchmove', this.onTouchMove, false);
	layer.removeEventListener('touchend', this.onTouchEnd, false);
	layer.removeEventListener('touchcancel', this.onTouchCancel, false);
};


/**
 * Check whether FastClick is needed.
 *
 * @param {Element} layer The layer to listen on
 */
FastClick.notNeeded = function(layer) {
	'use strict';
	var metaViewport;

	// Devices that don't support touch don't need FastClick
	if (typeof window.ontouchstart === 'undefined') {
		return true;
	}

	if ((/Chrome\/[0-9]+/).test(navigator.userAgent)) {

		// Chrome on Android with user-scalable="no" doesn't need FastClick (issue #89)
		if (FastClick.prototype.deviceIsAndroid) {
			metaViewport = document.querySelector('meta[name=viewport]');
			if (metaViewport && metaViewport.content.indexOf('user-scalable=no') !== -1) {
				return true;
			}

		// Chrome desktop doesn't need FastClick (issue #15)
		} else {
			return true;
		}
	}

	// IE10 with -ms-touch-action: none, which disables double-tap-to-zoom (issue #97)
	if (layer.style.msTouchAction === 'none') {
		return true;
	}

	return false;
};


/**
 * Factory method for creating a FastClick object
 *
 * @param {Element} layer The layer to listen on
 */
FastClick.attach = function(layer) {
	'use strict';
	return new FastClick(layer);
};


if (typeof define !== 'undefined' && define.amd) {

	// AMD. Register as an anonymous module.
	define('fastclick',[],function() {
		'use strict';
		return FastClick;
	});
} else if (typeof module !== 'undefined' && module.exports) {
	module.exports = FastClick.attach;
	module.exports.FastClick = FastClick;
} else {
	window.FastClick = FastClick;
}
;
//     Underscore.js 1.4.2
//     http://underscorejs.org
//     (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
//     Underscore may be freely distributed under the MIT license.

(function() {

  // Baseline setup
  // --------------

  // Establish the root object, `window` in the browser, or `global` on the server.
  var root = this;

  // Save the previous value of the `_` variable.
  var previousUnderscore = root._;

  // Establish the object that gets returned to break out of a loop iteration.
  var breaker = {};

  // Save bytes in the minified (but not gzipped) version:
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

  // Create quick reference variables for speed access to core prototypes.
  var push             = ArrayProto.push,
      slice            = ArrayProto.slice,
      concat           = ArrayProto.concat,
      unshift          = ArrayProto.unshift,
      toString         = ObjProto.toString,
      hasOwnProperty   = ObjProto.hasOwnProperty;

  // All **ECMAScript 5** native function implementations that we hope to use
  // are declared here.
  var
    nativeForEach      = ArrayProto.forEach,
    nativeMap          = ArrayProto.map,
    nativeReduce       = ArrayProto.reduce,
    nativeReduceRight  = ArrayProto.reduceRight,
    nativeFilter       = ArrayProto.filter,
    nativeEvery        = ArrayProto.every,
    nativeSome         = ArrayProto.some,
    nativeIndexOf      = ArrayProto.indexOf,
    nativeLastIndexOf  = ArrayProto.lastIndexOf,
    nativeIsArray      = Array.isArray,
    nativeKeys         = Object.keys,
    nativeBind         = FuncProto.bind;

  // Create a safe reference to the Underscore object for use below.
  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

  // Export the Underscore object for **Node.js**, with
  // backwards-compatibility for the old `require()` API. If we're in
  // the browser, add `_` as a global object via a string identifier,
  // for Closure Compiler "advanced" mode.
  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root['_'] = _;
  }

  // Current version.
  _.VERSION = '1.4.2';

  // Collection Functions
  // --------------------

  // The cornerstone, an `each` implementation, aka `forEach`.
  // Handles objects with the built-in `forEach`, arrays, and raw objects.
  // Delegates to **ECMAScript 5**'s native `forEach` if available.
  var each = _.each = _.forEach = function(obj, iterator, context) {
    if (obj == null) return;
    if (nativeForEach && obj.forEach === nativeForEach) {
      obj.forEach(iterator, context);
    } else if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        if (iterator.call(context, obj[i], i, obj) === breaker) return;
      }
    } else {
      for (var key in obj) {
        if (_.has(obj, key)) {
          if (iterator.call(context, obj[key], key, obj) === breaker) return;
        }
      }
    }
  };

  // Return the results of applying the iterator to each element.
  // Delegates to **ECMAScript 5**'s native `map` if available.
  _.map = _.collect = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
    each(obj, function(value, index, list) {
      results[results.length] = iterator.call(context, value, index, list);
    });
    return results;
  };

  // **Reduce** builds up a single result from a list of values, aka `inject`,
  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
    var initial = arguments.length > 2;
    if (obj == null) obj = [];
    if (nativeReduce && obj.reduce === nativeReduce) {
      if (context) iterator = _.bind(iterator, context);
      return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
    }
    each(obj, function(value, index, list) {
      if (!initial) {
        memo = value;
        initial = true;
      } else {
        memo = iterator.call(context, memo, value, index, list);
      }
    });
    if (!initial) throw new TypeError('Reduce of empty array with no initial value');
    return memo;
  };

  // The right-associative version of reduce, also known as `foldr`.
  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
    var initial = arguments.length > 2;
    if (obj == null) obj = [];
    if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
      if (context) iterator = _.bind(iterator, context);
      return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
    }
    var length = obj.length;
    if (length !== +length) {
      var keys = _.keys(obj);
      length = keys.length;
    }
    each(obj, function(value, index, list) {
      index = keys ? keys[--length] : --length;
      if (!initial) {
        memo = obj[index];
        initial = true;
      } else {
        memo = iterator.call(context, memo, obj[index], index, list);
      }
    });
    if (!initial) throw new TypeError('Reduce of empty array with no initial value');
    return memo;
  };

  // Return the first value which passes a truth test. Aliased as `detect`.
  _.find = _.detect = function(obj, iterator, context) {
    var result;
    any(obj, function(value, index, list) {
      if (iterator.call(context, value, index, list)) {
        result = value;
        return true;
      }
    });
    return result;
  };

  // Return all the elements that pass a truth test.
  // Delegates to **ECMAScript 5**'s native `filter` if available.
  // Aliased as `select`.
  _.filter = _.select = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
    each(obj, function(value, index, list) {
      if (iterator.call(context, value, index, list)) results[results.length] = value;
    });
    return results;
  };

  // Return all the elements for which a truth test fails.
  _.reject = function(obj, iterator, context) {
    return _.filter(obj, function(value, index, list) {
      return !iterator.call(context, value, index, list);
    }, context);
  };

  // Determine whether all of the elements match a truth test.
  // Delegates to **ECMAScript 5**'s native `every` if available.
  // Aliased as `all`.
  _.every = _.all = function(obj, iterator, context) {
    iterator || (iterator = _.identity);
    var result = true;
    if (obj == null) return result;
    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
    each(obj, function(value, index, list) {
      if (!(result = result && iterator.call(context, value, index, list))) return breaker;
    });
    return !!result;
  };

  // Determine if at least one element in the object matches a truth test.
  // Delegates to **ECMAScript 5**'s native `some` if available.
  // Aliased as `any`.
  var any = _.some = _.any = function(obj, iterator, context) {
    iterator || (iterator = _.identity);
    var result = false;
    if (obj == null) return result;
    if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
    each(obj, function(value, index, list) {
      if (result || (result = iterator.call(context, value, index, list))) return breaker;
    });
    return !!result;
  };

  // Determine if the array or object contains a given value (using `===`).
  // Aliased as `include`.
  _.contains = _.include = function(obj, target) {
    if (obj == null) return false;
    if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
    return any(obj, function(value) {
      return value === target;
    });
  };

  // Invoke a method (with arguments) on every item in a collection.
  _.invoke = function(obj, method) {
    var args = slice.call(arguments, 2);
    return _.map(obj, function(value) {
      return (_.isFunction(method) ? method : value[method]).apply(value, args);
    });
  };

  // Convenience version of a common use case of `map`: fetching a property.
  _.pluck = function(obj, key) {
    return _.map(obj, function(value){ return value[key]; });
  };

  // Convenience version of a common use case of `filter`: selecting only objects
  // with specific `key:value` pairs.
  _.where = function(obj, attrs) {
    if (_.isEmpty(attrs)) return [];
    return _.filter(obj, function(value) {
      for (var key in attrs) {
        if (attrs[key] !== value[key]) return false;
      }
      return true;
    });
  };

  // Return the maximum element or (element-based computation).
  // Can't optimize arrays of integers longer than 65,535 elements.
  // See: https://bugs.webkit.org/show_bug.cgi?id=80797
  _.max = function(obj, iterator, context) {
    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
      return Math.max.apply(Math, obj);
    }
    if (!iterator && _.isEmpty(obj)) return -Infinity;
    var result = {computed : -Infinity};
    each(obj, function(value, index, list) {
      var computed = iterator ? iterator.call(context, value, index, list) : value;
      computed >= result.computed && (result = {value : value, computed : computed});
    });
    return result.value;
  };

  // Return the minimum element (or element-based computation).
  _.min = function(obj, iterator, context) {
    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
      return Math.min.apply(Math, obj);
    }
    if (!iterator && _.isEmpty(obj)) return Infinity;
    var result = {computed : Infinity};
    each(obj, function(value, index, list) {
      var computed = iterator ? iterator.call(context, value, index, list) : value;
      computed < result.computed && (result = {value : value, computed : computed});
    });
    return result.value;
  };

  // Shuffle an array.
  _.shuffle = function(obj) {
    var rand;
    var index = 0;
    var shuffled = [];
    each(obj, function(value) {
      rand = _.random(index++);
      shuffled[index - 1] = shuffled[rand];
      shuffled[rand] = value;
    });
    return shuffled;
  };

  // An internal function to generate lookup iterators.
  var lookupIterator = function(value) {
    return _.isFunction(value) ? value : function(obj){ return obj[value]; };
  };

  // Sort the object's values by a criterion produced by an iterator.
  _.sortBy = function(obj, value, context) {
    var iterator = lookupIterator(value);
    return _.pluck(_.map(obj, function(value, index, list) {
      return {
        value : value,
        index : index,
        criteria : iterator.call(context, value, index, list)
      };
    }).sort(function(left, right) {
      var a = left.criteria;
      var b = right.criteria;
      if (a !== b) {
        if (a > b || a === void 0) return 1;
        if (a < b || b === void 0) return -1;
      }
      return left.index < right.index ? -1 : 1;
    }), 'value');
  };

  // An internal function used for aggregate "group by" operations.
  var group = function(obj, value, context, behavior) {
    var result = {};
    var iterator = lookupIterator(value);
    each(obj, function(value, index) {
      var key = iterator.call(context, value, index, obj);
      behavior(result, key, value);
    });
    return result;
  };

  // Groups the object's values by a criterion. Pass either a string attribute
  // to group by, or a function that returns the criterion.
  _.groupBy = function(obj, value, context) {
    return group(obj, value, context, function(result, key, value) {
      (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
    });
  };

  // Counts instances of an object that group by a certain criterion. Pass
  // either a string attribute to count by, or a function that returns the
  // criterion.
  _.countBy = function(obj, value, context) {
    return group(obj, value, context, function(result, key, value) {
      if (!_.has(result, key)) result[key] = 0;
      result[key]++;
    });
  };

  // Use a comparator function to figure out the smallest index at which
  // an object should be inserted so as to maintain order. Uses binary search.
  _.sortedIndex = function(array, obj, iterator, context) {
    iterator = iterator == null ? _.identity : lookupIterator(iterator);
    var value = iterator.call(context, obj);
    var low = 0, high = array.length;
    while (low < high) {
      var mid = (low + high) >>> 1;
      iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
    }
    return low;
  };

  // Safely convert anything iterable into a real, live array.
  _.toArray = function(obj) {
    if (!obj) return [];
    if (obj.length === +obj.length) return slice.call(obj);
    return _.values(obj);
  };

  // Return the number of elements in an object.
  _.size = function(obj) {
    if (obj == null) return 0;
    return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
  };

  // Array Functions
  // ---------------

  // Get the first element of an array. Passing **n** will return the first N
  // values in the array. Aliased as `head` and `take`. The **guard** check
  // allows it to work with `_.map`.
  _.first = _.head = _.take = function(array, n, guard) {
    if (array == null) return void 0;
    return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
  };

  // Returns everything but the last entry of the array. Especially useful on
  // the arguments object. Passing **n** will return all the values in
  // the array, excluding the last N. The **guard** check allows it to work with
  // `_.map`.
  _.initial = function(array, n, guard) {
    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
  };

  // Get the last element of an array. Passing **n** will return the last N
  // values in the array. The **guard** check allows it to work with `_.map`.
  _.last = function(array, n, guard) {
    if (array == null) return void 0;
    if ((n != null) && !guard) {
      return slice.call(array, Math.max(array.length - n, 0));
    } else {
      return array[array.length - 1];
    }
  };

  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
  // Especially useful on the arguments object. Passing an **n** will return
  // the rest N values in the array. The **guard**
  // check allows it to work with `_.map`.
  _.rest = _.tail = _.drop = function(array, n, guard) {
    return slice.call(array, (n == null) || guard ? 1 : n);
  };

  // Trim out all falsy values from an array.
  _.compact = function(array) {
    return _.filter(array, function(value){ return !!value; });
  };

  // Internal implementation of a recursive `flatten` function.
  var flatten = function(input, shallow, output) {
    each(input, function(value) {
      if (_.isArray(value)) {
        shallow ? push.apply(output, value) : flatten(value, shallow, output);
      } else {
        output.push(value);
      }
    });
    return output;
  };

  // Return a completely flattened version of an array.
  _.flatten = function(array, shallow) {
    return flatten(array, shallow, []);
  };

  // Return a version of the array that does not contain the specified value(s).
  _.without = function(array) {
    return _.difference(array, slice.call(arguments, 1));
  };

  // Produce a duplicate-free version of the array. If the array has already
  // been sorted, you have the option of using a faster algorithm.
  // Aliased as `unique`.
  _.uniq = _.unique = function(array, isSorted, iterator, context) {
    if (_.isFunction(isSorted)) {
      context = iterator;
      iterator = isSorted;
      isSorted = false;
    }
    var initial = iterator ? _.map(array, iterator, context) : array;
    var results = [];
    var seen = [];
    each(initial, function(value, index) {
      if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
        seen.push(value);
        results.push(array[index]);
      }
    });
    return results;
  };

  // Produce an array that contains the union: each distinct element from all of
  // the passed-in arrays.
  _.union = function() {
    return _.uniq(concat.apply(ArrayProto, arguments));
  };

  // Produce an array that contains every item shared between all the
  // passed-in arrays.
  _.intersection = function(array) {
    var rest = slice.call(arguments, 1);
    return _.filter(_.uniq(array), function(item) {
      return _.every(rest, function(other) {
        return _.indexOf(other, item) >= 0;
      });
    });
  };

  // Take the difference between one array and a number of other arrays.
  // Only the elements present in just the first array will remain.
  _.difference = function(array) {
    var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
    return _.filter(array, function(value){ return !_.contains(rest, value); });
  };

  // Zip together multiple lists into a single array -- elements that share
  // an index go together.
  _.zip = function() {
    var args = slice.call(arguments);
    var length = _.max(_.pluck(args, 'length'));
    var results = new Array(length);
    for (var i = 0; i < length; i++) {
      results[i] = _.pluck(args, "" + i);
    }
    return results;
  };

  // Converts lists into objects. Pass either a single array of `[key, value]`
  // pairs, or two parallel arrays of the same length -- one of keys, and one of
  // the corresponding values.
  _.object = function(list, values) {
    if (list == null) return {};
    var result = {};
    for (var i = 0, l = list.length; i < l; i++) {
      if (values) {
        result[list[i]] = values[i];
      } else {
        result[list[i][0]] = list[i][1];
      }
    }
    return result;
  };

  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
  // we need this function. Return the position of the first occurrence of an
  // item in an array, or -1 if the item is not included in the array.
  // Delegates to **ECMAScript 5**'s native `indexOf` if available.
  // If the array is large and already in sort order, pass `true`
  // for **isSorted** to use binary search.
  _.indexOf = function(array, item, isSorted) {
    if (array == null) return -1;
    var i = 0, l = array.length;
    if (isSorted) {
      if (typeof isSorted == 'number') {
        i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
      } else {
        i = _.sortedIndex(array, item);
        return array[i] === item ? i : -1;
      }
    }
    if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
    for (; i < l; i++) if (array[i] === item) return i;
    return -1;
  };

  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
  _.lastIndexOf = function(array, item, from) {
    if (array == null) return -1;
    var hasIndex = from != null;
    if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
      return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
    }
    var i = (hasIndex ? from : array.length);
    while (i--) if (array[i] === item) return i;
    return -1;
  };

  // Generate an integer Array containing an arithmetic progression. A port of
  // the native Python `range()` function. See
  // [the Python documentation](http://docs.python.org/library/functions.html#range).
  _.range = function(start, stop, step) {
    if (arguments.length <= 1) {
      stop = start || 0;
      start = 0;
    }
    step = arguments[2] || 1;

    var len = Math.max(Math.ceil((stop - start) / step), 0);
    var idx = 0;
    var range = new Array(len);

    while(idx < len) {
      range[idx++] = start;
      start += step;
    }

    return range;
  };

  // Function (ahem) Functions
  // ------------------

  // Reusable constructor function for prototype setting.
  var ctor = function(){};

  // Create a function bound to a given object (assigning `this`, and arguments,
  // optionally). Binding with arguments is also known as `curry`.
  // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
  // We check for `func.bind` first, to fail fast when `func` is undefined.
  _.bind = function bind(func, context) {
    var bound, args;
    if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
    if (!_.isFunction(func)) throw new TypeError;
    args = slice.call(arguments, 2);
    return bound = function() {
      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
      ctor.prototype = func.prototype;
      var self = new ctor;
      var result = func.apply(self, args.concat(slice.call(arguments)));
      if (Object(result) === result) return result;
      return self;
    };
  };

  // Bind all of an object's methods to that object. Useful for ensuring that
  // all callbacks defined on an object belong to it.
  _.bindAll = function(obj) {
    var funcs = slice.call(arguments, 1);
    if (funcs.length == 0) funcs = _.functions(obj);
    each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
    return obj;
  };

  // Memoize an expensive function by storing its results.
  _.memoize = function(func, hasher) {
    var memo = {};
    hasher || (hasher = _.identity);
    return function() {
      var key = hasher.apply(this, arguments);
      return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
    };
  };

  // Delays a function for the given number of milliseconds, and then calls
  // it with the arguments supplied.
  _.delay = function(func, wait) {
    var args = slice.call(arguments, 2);
    return setTimeout(function(){ return func.apply(null, args); }, wait);
  };

  // Defers a function, scheduling it to run after the current call stack has
  // cleared.
  _.defer = function(func) {
    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
  };

  // Returns a function, that, when invoked, will only be triggered at most once
  // during a given window of time.
  _.throttle = function(func, wait) {
    var context, args, timeout, result;
    var previous = 0;
    var later = function() {
      previous = new Date;
      timeout = null;
      result = func.apply(context, args);
    };
    return function() {
      var now = new Date;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0) {
        clearTimeout(timeout);
        previous = now;
        result = func.apply(context, args);
      } else if (!timeout) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

  // Returns a function, that, as long as it continues to be invoked, will not
  // be triggered. The function will be called after it stops being called for
  // N milliseconds. If `immediate` is passed, trigger the function on the
  // leading edge, instead of the trailing.
  _.debounce = function(func, wait, immediate) {
    var timeout, result;
    return function() {
      var context = this, args = arguments;
      var later = function() {
        timeout = null;
        if (!immediate) result = func.apply(context, args);
      };
      var callNow = immediate && !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
      if (callNow) result = func.apply(context, args);
      return result;
    };
  };

  // Returns a function that will be executed at most one time, no matter how
  // often you call it. Useful for lazy initialization.
  _.once = function(func) {
    var ran = false, memo;
    return function() {
      if (ran) return memo;
      ran = true;
      memo = func.apply(this, arguments);
      func = null;
      return memo;
    };
  };

  // Returns the first function passed as an argument to the second,
  // allowing you to adjust arguments, run code before and after, and
  // conditionally execute the original function.
  _.wrap = function(func, wrapper) {
    return function() {
      var args = [func];
      push.apply(args, arguments);
      return wrapper.apply(this, args);
    };
  };

  // Returns a function that is the composition of a list of functions, each
  // consuming the return value of the function that follows.
  _.compose = function() {
    var funcs = arguments;
    return function() {
      var args = arguments;
      for (var i = funcs.length - 1; i >= 0; i--) {
        args = [funcs[i].apply(this, args)];
      }
      return args[0];
    };
  };

  // Returns a function that will only be executed after being called N times.
  _.after = function(times, func) {
    if (times <= 0) return func();
    return function() {
      if (--times < 1) {
        return func.apply(this, arguments);
      }
    };
  };

  // Object Functions
  // ----------------

  // Retrieve the names of an object's properties.
  // Delegates to **ECMAScript 5**'s native `Object.keys`
  _.keys = nativeKeys || function(obj) {
    if (obj !== Object(obj)) throw new TypeError('Invalid object');
    var keys = [];
    for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
    return keys;
  };

  // Retrieve the values of an object's properties.
  _.values = function(obj) {
    var values = [];
    for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
    return values;
  };

  // Convert an object into a list of `[key, value]` pairs.
  _.pairs = function(obj) {
    var pairs = [];
    for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
    return pairs;
  };

  // Invert the keys and values of an object. The values must be serializable.
  _.invert = function(obj) {
    var result = {};
    for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
    return result;
  };

  // Return a sorted list of the function names available on the object.
  // Aliased as `methods`
  _.functions = _.methods = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };

  // Extend a given object with all the properties in passed-in object(s).
  _.extend = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      for (var prop in source) {
        obj[prop] = source[prop];
      }
    });
    return obj;
  };

  // Return a copy of the object only containing the whitelisted properties.
  _.pick = function(obj) {
    var copy = {};
    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
    each(keys, function(key) {
      if (key in obj) copy[key] = obj[key];
    });
    return copy;
  };

   // Return a copy of the object without the blacklisted properties.
  _.omit = function(obj) {
    var copy = {};
    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
    for (var key in obj) {
      if (!_.contains(keys, key)) copy[key] = obj[key];
    }
    return copy;
  };

  // Fill in a given object with default properties.
  _.defaults = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      for (var prop in source) {
        if (obj[prop] == null) obj[prop] = source[prop];
      }
    });
    return obj;
  };

  // Create a (shallow-cloned) duplicate of an object.
  _.clone = function(obj) {
    if (!_.isObject(obj)) return obj;
    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  };

  // Invokes interceptor with the obj, and then returns obj.
  // The primary purpose of this method is to "tap into" a method chain, in
  // order to perform operations on intermediate results within the chain.
  _.tap = function(obj, interceptor) {
    interceptor(obj);
    return obj;
  };

  // Internal recursive comparison function for `isEqual`.
  var eq = function(a, b, aStack, bStack) {
    // Identical objects are equal. `0 === -0`, but they aren't identical.
    // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
    if (a === b) return a !== 0 || 1 / a == 1 / b;
    // A strict comparison is necessary because `null == undefined`.
    if (a == null || b == null) return a === b;
    // Unwrap any wrapped objects.
    if (a instanceof _) a = a._wrapped;
    if (b instanceof _) b = b._wrapped;
    // Compare `[[Class]]` names.
    var className = toString.call(a);
    if (className != toString.call(b)) return false;
    switch (className) {
      // Strings, numbers, dates, and booleans are compared by value.
      case '[object String]':
        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
        // equivalent to `new String("5")`.
        return a == String(b);
      case '[object Number]':
        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
        // other numeric values.
        return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
      case '[object Date]':
      case '[object Boolean]':
        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
        // millisecond representations. Note that invalid dates with millisecond representations
        // of `NaN` are not equivalent.
        return +a == +b;
      // RegExps are compared by their source patterns and flags.
      case '[object RegExp]':
        return a.source == b.source &&
               a.global == b.global &&
               a.multiline == b.multiline &&
               a.ignoreCase == b.ignoreCase;
    }
    if (typeof a != 'object' || typeof b != 'object') return false;
    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
    var length = aStack.length;
    while (length--) {
      // Linear search. Performance is inversely proportional to the number of
      // unique nested structures.
      if (aStack[length] == a) return bStack[length] == b;
    }
    // Add the first object to the stack of traversed objects.
    aStack.push(a);
    bStack.push(b);
    var size = 0, result = true;
    // Recursively compare objects and arrays.
    if (className == '[object Array]') {
      // Compare array lengths to determine if a deep comparison is necessary.
      size = a.length;
      result = size == b.length;
      if (result) {
        // Deep compare the contents, ignoring non-numeric properties.
        while (size--) {
          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
        }
      }
    } else {
      // Objects with different constructors are not equivalent, but `Object`s
      // from different frames are.
      var aCtor = a.constructor, bCtor = b.constructor;
      if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
                               _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
        return false;
      }
      // Deep compare objects.
      for (var key in a) {
        if (_.has(a, key)) {
          // Count the expected number of properties.
          size++;
          // Deep compare each member.
          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
        }
      }
      // Ensure that both objects contain the same number of properties.
      if (result) {
        for (key in b) {
          if (_.has(b, key) && !(size--)) break;
        }
        result = !size;
      }
    }
    // Remove the first object from the stack of traversed objects.
    aStack.pop();
    bStack.pop();
    return result;
  };

  // Perform a deep comparison to check if two objects are equal.
  _.isEqual = function(a, b) {
    return eq(a, b, [], []);
  };

  // Is a given array, string, or object empty?
  // An "empty" object has no enumerable own-properties.
  _.isEmpty = function(obj) {
    if (obj == null) return true;
    if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
    for (var key in obj) if (_.has(obj, key)) return false;
    return true;
  };

  // Is a given value a DOM element?
  _.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
  };

  // Is a given value an array?
  // Delegates to ECMA5's native Array.isArray
  _.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) == '[object Array]';
  };

  // Is a given variable an object?
  _.isObject = function(obj) {
    return obj === Object(obj);
  };

  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) == '[object ' + name + ']';
    };
  });

  // Define a fallback version of the method in browsers (ahem, IE), where
  // there isn't any inspectable "Arguments" type.
  if (!_.isArguments(arguments)) {
    _.isArguments = function(obj) {
      return !!(obj && _.has(obj, 'callee'));
    };
  }

  // Optimize `isFunction` if appropriate.
  if (typeof (/./) !== 'function') {
    _.isFunction = function(obj) {
      return typeof obj === 'function';
    };
  }

  // Is a given object a finite number?
  _.isFinite = function(obj) {
    return isFinite( obj ) && !isNaN( parseFloat(obj) );
  };

  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
  _.isNaN = function(obj) {
    return _.isNumber(obj) && obj != +obj;
  };

  // Is a given value a boolean?
  _.isBoolean = function(obj) {
    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
  };

  // Is a given value equal to null?
  _.isNull = function(obj) {
    return obj === null;
  };

  // Is a given variable undefined?
  _.isUndefined = function(obj) {
    return obj === void 0;
  };

  // Shortcut function for checking if an object has a given property directly
  // on itself (in other words, not on a prototype).
  _.has = function(obj, key) {
    return hasOwnProperty.call(obj, key);
  };

  // Utility Functions
  // -----------------

  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  // previous owner. Returns a reference to the Underscore object.
  _.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };

  // Keep the identity function around for default iterators.
  _.identity = function(value) {
    return value;
  };

  // Run a function **n** times.
  _.times = function(n, iterator, context) {
    for (var i = 0; i < n; i++) iterator.call(context, i);
  };

  // Return a random integer between min and max (inclusive).
  _.random = function(min, max) {
    if (max == null) {
      max = min;
      min = 0;
    }
    return min + (0 | Math.random() * (max - min + 1));
  };

  // List of HTML entities for escaping.
  var entityMap = {
    escape: {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#x27;',
      '/': '&#x2F;'
    }
  };
  entityMap.unescape = _.invert(entityMap.escape);

  // Regexes containing the keys and values listed immediately above.
  var entityRegexes = {
    escape:   new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
    unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
  };

  // Functions for escaping and unescaping strings to/from HTML interpolation.
  _.each(['escape', 'unescape'], function(method) {
    _[method] = function(string) {
      if (string == null) return '';
      return ('' + string).replace(entityRegexes[method], function(match) {
        return entityMap[method][match];
      });
    };
  });

  // If the value of the named property is a function then invoke it;
  // otherwise, return it.
  _.result = function(object, property) {
    if (object == null) return null;
    var value = object[property];
    return _.isFunction(value) ? value.call(object) : value;
  };

  // Add your own custom functions to the Underscore object.
  _.mixin = function(obj) {
    each(_.functions(obj), function(name){
      var func = _[name] = obj[name];
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        return result.call(this, func.apply(_, args));
      };
    });
  };

  // Generate a unique integer id (unique within the entire client session).
  // Useful for temporary DOM ids.
  var idCounter = 0;
  _.uniqueId = function(prefix) {
    var id = idCounter++;
    return prefix ? prefix + id : id;
  };

  // By default, Underscore uses ERB-style template delimiters, change the
  // following template settings to use alternative delimiters.
  _.templateSettings = {
    evaluate    : /<%([\s\S]+?)%>/g,
    interpolate : /<%=([\s\S]+?)%>/g,
    escape      : /<%-([\s\S]+?)%>/g
  };

  // When customizing `templateSettings`, if you don't want to define an
  // interpolation, evaluation or escaping regex, we need one that is
  // guaranteed not to match.
  var noMatch = /(.)^/;

  // Certain characters need to be escaped so that they can be put into a
  // string literal.
  var escapes = {
    "'":      "'",
    '\\':     '\\',
    '\r':     'r',
    '\n':     'n',
    '\t':     't',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;

  // JavaScript micro-templating, similar to John Resig's implementation.
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
  // and correctly escapes quotes within interpolated code.
  _.template = function(text, data, settings) {
    settings = _.defaults({}, settings, _.templateSettings);

    // Combine delimiters into one regular expression via alternation.
    var matcher = new RegExp([
      (settings.escape || noMatch).source,
      (settings.interpolate || noMatch).source,
      (settings.evaluate || noMatch).source
    ].join('|') + '|$', 'g');

    // Compile the template source, escaping string literals appropriately.
    var index = 0;
    var source = "__p+='";
    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
      source += text.slice(index, offset)
        .replace(escaper, function(match) { return '\\' + escapes[match]; });
      source +=
        escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
        interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" :
        evaluate ? "';\n" + evaluate + "\n__p+='" : '';
      index = offset + match.length;
    });
    source += "';\n";

    // If a variable is not specified, place data values in local scope.
    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

    source = "var __t,__p='',__j=Array.prototype.join," +
      "print=function(){__p+=__j.call(arguments,'');};\n" +
      source + "return __p;\n";

    try {
      var render = new Function(settings.variable || 'obj', '_', source);
    } catch (e) {
      e.source = source;
      throw e;
    }

    if (data) return render(data, _);
    var template = function(data) {
      return render.call(this, data, _);
    };

    // Provide the compiled function source as a convenience for precompilation.
    template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';

    return template;
  };

  // Add a "chain" function, which will delegate to the wrapper.
  _.chain = function(obj) {
    return _(obj).chain();
  };

  // OOP
  // ---------------
  // If Underscore is called as a function, it returns a wrapped object that
  // can be used OO-style. This wrapper holds altered versions of all the
  // underscore functions. Wrapped objects may be chained.

  // Helper function to continue chaining intermediate results.
  var result = function(obj) {
    return this._chain ? _(obj).chain() : obj;
  };

  // Add all of the Underscore functions to the wrapper object.
  _.mixin(_);

  // Add all mutator Array functions to the wrapper.
  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      var obj = this._wrapped;
      method.apply(obj, arguments);
      if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
      return result.call(this, obj);
    };
  });

  // Add all accessor Array functions to the wrapper.
  each(['concat', 'join', 'slice'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      return result.call(this, method.apply(this._wrapped, arguments));
    };
  });

  _.extend(_.prototype, {

    // Start chaining a wrapped Underscore object.
    chain: function() {
      this._chain = true;
      return this;
    },

    // Extracts the result from a wrapped and chained object.
    value: function() {
      return this._wrapped;
    }

  });

}).call(this);

define("underscore", (function (global) {
    return function () {
        var ret, fn;
        return ret || global._;
    };
}(this)));

/*global define: false console: true */

define('common/console',['require','lab.config'],function (require) {
  // Dependencies.
  var labConfig = require('lab.config'),

      // Object to be returned.
      publicAPI,
      cons,
      emptyFunction = function () {};

  // Prevent a console.log from blowing things up if we are on a browser that
  // does not support it ... like IE9.
  if (typeof console === 'undefined') {
    console = {};
    if (window) window.console = console;
  }

  // Assign shortcut.
  cons = console;
  // Make sure that every method is defined.
  if (cons.log === undefined)
    cons.log = emptyFunction;
  if (cons.info === undefined)
    cons.info = emptyFunction;
  if (cons.warn === undefined)
    cons.warn = emptyFunction;
  if (cons.error === undefined)
    cons.error = emptyFunction;
  if (cons.time === undefined)
    cons.time = emptyFunction;
  if (cons.timeEnd === undefined)
    cons.timeEnd = emptyFunction;

  // Make sure that every method has access to an 'apply' method
  // This is a hack for IE9 and IE10 when using the built-in developer tools.
  // See: http://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function
  if (cons.log.apply === undefined)
    cons.log = Function.prototype.bind.call(console.log, console);
  if (cons.info.apply === undefined)
    cons.info = Function.prototype.bind.call(console.info, console);
  if (cons.warn.apply === undefined)
    cons.warn = Function.prototype.bind.call(console.warn, console);
  if (cons.error.apply === undefined)
    cons.error = Function.prototype.bind.call(console.error, console);
  if (cons.time.apply === undefined)
    cons.time = Function.prototype.bind.call(console.time, console);
  if (cons.timeEnd.apply === undefined)
    cons.timeEnd = Function.prototype.bind.call(console.timeEnd, console);

  publicAPI = {
    log: function () {
      if (labConfig.logging)
        cons.log.apply(cons, arguments);
    },
    info: function () {
      if (labConfig.logging)
        cons.info.apply(cons, arguments);
    },
    warn: function () {
      if (labConfig.logging)
        cons.warn.apply(cons, arguments);
    },
    error: function () {
      if (labConfig.logging)
        cons.error.apply(cons, arguments);
    },
    time: function () {
      if (labConfig.tracing)
        cons.time.apply(cons, arguments);
    },
    timeEnd: function () {
      if (labConfig.tracing)
        cons.timeEnd.apply(cons, arguments);
    }
  };

  return publicAPI;
});

/*global define: false alert: false */

/**
  Tiny module providing global way to show errors to user.

  It's better to use module, as in the future, we may want to replace basic
  alert with more sophisticated solution (for example jQuery UI dialog).
*/
define('common/alert',['require','common/console'],function (require) {
  // Dependencies.
  var console = require('common/console'),

      // Try to use global alert. If it's not available, use console.error (node.js).
      alertFunc = typeof alert !== 'undefined' ? alert : console.error;

  return function alert(msg) {
    alertFunc(msg);
  };
});

/*global define: false, $: false */

// For now, only defaultValue, readOnly and immutable
// meta-properties are supported.
define('common/validator',['require','arrays'],function(require) {

  var arrays = require('arrays');

  // Create a new object, that prototypically inherits from the Error constructor.
  // It provides a direct information which property of the input caused an error.
  function ValidationError(prop, message) {
    this.prop = prop;
    this.message = message;
  }
  ValidationError.prototype = new Error();
  ValidationError.prototype.constructor = ValidationError;

  function isObject(prop) {
    // Note that typeof null is also equal to "object", so we have to check it.
    return prop !== null && typeof prop === "object";
  }

  function checkConflicts(input, propName, conflictingProps) {
    var i, len;
    for (i = 0, len = conflictingProps.length; i < len; i++) {
      if (input.hasOwnProperty(conflictingProps[i])) {
        throw new ValidationError(propName, "Properties set contains conflicting properties: " +
          conflictingProps[i] + " and " + propName);
      }
    }
  }

  function validateSingleProperty(propertyMetadata, prop, value, ignoreImmutable) {
    if (propertyMetadata.readOnly) {
      throw new ValidationError(prop, "Properties set tries to overwrite read-only property " + prop);
    }
    if (!ignoreImmutable && propertyMetadata.immutable) {
      throw new ValidationError(prop, "Properties set tries to overwrite immutable property " + prop);
    }
    // Use custom validate function defined in metadata if provided.
    return propertyMetadata.validate ? propertyMetadata.validate(value) : value;
  }

  return {

    // Basic validation.
    // Check if provided 'input' hash doesn't try to overwrite properties
    // which are marked as read-only or immutable. Don't take into account
    // 'defaultValue' as the 'input' hash is allowed to be incomplete.
    // It should be used *only* for update of an object.
    // While creating new object, use validateCompleteness() instead!
    validate: function (metadata, input, ignoreImmutable) {
      var result = {},
          prop, propMetadata;

      if (arguments.length < 2) {
        throw new Error("Incorrect usage. Provide metadata and hash which should be validated.");
      }

      for (prop in input) {
        if (input.hasOwnProperty(prop)) {
          // Try to get meta-data for this property.
          propMetadata = metadata[prop];
          // Continue only if the property is listed in meta-data.
          if (typeof propMetadata !== "undefined") {
            input[prop] = validateSingleProperty(propMetadata, prop, input[prop], ignoreImmutable);
            if (propMetadata.conflictsWith) {
              checkConflicts(input, prop, propMetadata.conflictsWith);
            }
            result[prop] = input[prop];
          }
        }
      }
      return result;
    },

    validateSingleProperty: validateSingleProperty,

    propertyIsWritable: function(propertyMetadata) {
      // Note that immutable properties are writable, they just have to be
      return ! propertyMetadata.readOnly;
    },

    propertyChangeInvalidates: function(propertyMetadata) {
      // Default to true for safety.
      if (typeof propertyMetadata.propertyChangeInvalidates === "undefined") {
        return true;
      }
      return !!propertyMetadata.propertyChangeInvalidates;
    },

    // Complete validation.
    // Assume that provided 'input' hash is used for creation of new
    // object. Start with checking if all required values are provided,
    // and using default values if they are provided.
    // Later perform basic validation.
    validateCompleteness: function (metadata, input, opts) {
      var result = {},
          includeOnlySerializedProperties = opts && opts.includeOnlySerializedProperties,
          prop, propMetadata, defVal;

      if (arguments.length < 2) {
        throw new Error("Incorrect usage. Provide metadata and hash which should be validated.");
      }

      for (prop in metadata) {
        if (metadata.hasOwnProperty(prop)) {
          propMetadata = metadata[prop];
          // require explicit check for serialize === false, because the default value is true.
          if (includeOnlySerializedProperties && propMetadata.serialize === false) {
            continue;
          }

          defVal = propMetadata.defaultValue;

          if (typeof input[prop] === "undefined") {
            // Value is not declared in the input data.
            if (propMetadata.required === true) {
              throw new ValidationError(prop, "Properties set is missing required property " + prop);
            } else if (arrays.isArray(defVal)) {
              // Copy an array defined as a default value.
              // Do not use instance defined in metadata.
              result[prop] = arrays.copy(defVal, []);
            } else if (isObject(defVal)) {
              // Copy an object defined as a default value. Do not use instance defined in metadata.
              result[prop] = $.extend(true, {}, defVal);
            } else if (typeof defVal !== "undefined") {
              // If it's basic type, just set value.
              result[prop] = defVal;
            }
          } else if (!arrays.isArray(input[prop]) && isObject(input[prop]) && isObject(defVal)) {
            // Note that typeof [] is also "object" - that is the reason of the isArray() check.
            result[prop] = $.extend(true, {}, defVal, input[prop]);
          } else if (arrays.isArray(input[prop])) {
            // Deep copy of an array.
            result[prop] = $.extend(true, [], input[prop]);
          } else {
            // Basic type like number, so '=' is enough.
            result[prop] = input[prop];
          }
        }
      }

      // Perform standard check like for hash meant to update object.
      // However, ignore immutable check, as these properties are supposed
      // to create a new object.
      return this.validate(metadata, result, true);
    },

    // Expose ValidationError. It can be useful for the custom validation routines.
    ValidationError: ValidationError
  };
});

// i18next, v1.7.3
// Copyright (c)2014 Jan Mühlemann (jamuhl).
// Distributed under MIT license
// http://i18next.com
(function (root, factory) {
    if (typeof exports === 'object') {

        module.exports = factory();

    } else if (typeof define === 'function' && define.amd) {

        define('i18next',[], factory);

    } 
}(this, function () {

    // add indexOf to non ECMA-262 standard compliant browsers
    if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
            "use strict";
            if (this == null) {
                throw new TypeError();
            }
            var t = Object(this);
            var len = t.length >>> 0;
            if (len === 0) {
                return -1;
            }
            var n = 0;
            if (arguments.length > 0) {
                n = Number(arguments[1]);
                if (n != n) { // shortcut for verifying if it's NaN
                    n = 0;
                } else if (n != 0 && n != Infinity && n != -Infinity) {
                    n = (n > 0 || -1) * Math.floor(Math.abs(n));
                }
            }
            if (n >= len) {
                return -1;
            }
            var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
            for (; k < len; k++) {
                if (k in t && t[k] === searchElement) {
                    return k;
                }
            }
            return -1;
        }
    }
    
    // add lastIndexOf to non ECMA-262 standard compliant browsers
    if (!Array.prototype.lastIndexOf) {
        Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) {
            "use strict";
            if (this == null) {
                throw new TypeError();
            }
            var t = Object(this);
            var len = t.length >>> 0;
            if (len === 0) {
                return -1;
            }
            var n = len;
            if (arguments.length > 1) {
                n = Number(arguments[1]);
                if (n != n) {
                    n = 0;
                } else if (n != 0 && n != (1 / 0) && n != -(1 / 0)) {
                    n = (n > 0 || -1) * Math.floor(Math.abs(n));
                }
            }
            var k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n);
            for (; k >= 0; k--) {
                if (k in t && t[k] === searchElement) {
                    return k;
                }
            }
            return -1;
        };
    }
    
    // Add string trim for IE8.
    if (typeof String.prototype.trim !== 'function') {
        String.prototype.trim = function() {
            return this.replace(/^\s+|\s+$/g, ''); 
        }
    }

    var $ = undefined
        , i18n = {}
        , resStore = {}
        , currentLng
        , replacementCounter = 0
        , languages = []
        , initialized = false;

    // defaults
    var o = {
        lng: undefined,
        load: 'all',
        preload: [],
        lowerCaseLng: false,
        returnObjectTrees: false,
        fallbackLng: ['dev'],
        fallbackNS: [],
        detectLngQS: 'setLng',
        ns: 'translation',
        fallbackOnNull: true,
        fallbackOnEmpty: false,
        fallbackToDefaultNS: false,
        nsseparator: ':',
        keyseparator: '.',
        selectorAttr: 'data-i18n',
        debug: false,
        
        resGetPath: 'locales/__lng__/__ns__.json',
        resPostPath: 'locales/add/__lng__/__ns__',
    
        getAsync: true,
        postAsync: true,
    
        resStore: undefined,
        useLocalStorage: false,
        localStorageExpirationTime: 7*24*60*60*1000,
    
        dynamicLoad: false,
        sendMissing: false,
        sendMissingTo: 'fallback', // current | all
        sendType: 'POST',
    
        interpolationPrefix: '__',
        interpolationSuffix: '__',
        reusePrefix: '$t(',
        reuseSuffix: ')',
        pluralSuffix: '_plural',
        pluralNotFound: ['plural_not_found', Math.random()].join(''),
        contextNotFound: ['context_not_found', Math.random()].join(''),
        escapeInterpolation: false,
    
        setJqueryExt: true,
        defaultValueFromContent: true,
        useDataAttrOptions: false,
        cookieExpirationTime: undefined,
        useCookie: true,
        cookieName: 'i18next',
        cookieDomain: undefined,
    
        objectTreeKeyHandler: undefined,
        postProcess: undefined,
        parseMissingKey: undefined,
    
        shortcutFunction: 'sprintf' // or: defaultValue
    };
    function _extend(target, source) {
        if (!source || typeof source === 'function') {
            return target;
        }
    
        for (var attr in source) { target[attr] = source[attr]; }
        return target;
    }
    
    function _each(object, callback, args) {
        var name, i = 0,
            length = object.length,
            isObj = length === undefined || Object.prototype.toString.apply(object) !== '[object Array]' || typeof object === "function";
    
        if (args) {
            if (isObj) {
                for (name in object) {
                    if (callback.apply(object[name], args) === false) {
                        break;
                    }
                }
            } else {
                for ( ; i < length; ) {
                    if (callback.apply(object[i++], args) === false) {
                        break;
                    }
                }
            }
    
        // A special, fast, case for the most common use of each
        } else {
            if (isObj) {
                for (name in object) {
                    if (callback.call(object[name], name, object[name]) === false) {
                        break;
                    }
                }
            } else {
                for ( ; i < length; ) {
                    if (callback.call(object[i], i, object[i++]) === false) {
                        break;
                    }
                }
            }
        }
    
        return object;
    }
    
    var _entityMap = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': '&quot;',
        "'": '&#39;',
        "/": '&#x2F;'
    };
    
    function _escape(data) {
        if (typeof data === 'string') {
            return data.replace(/[&<>"'\/]/g, function (s) {
                return _entityMap[s];
            });
        }else{
            return data;
        }
    }
    
    function _ajax(options) {
    
        // v0.5.0 of https://github.com/goloroden/http.js
        var getXhr = function (callback) {
            // Use the native XHR object if the browser supports it.
            if (window.XMLHttpRequest) {
                return callback(null, new XMLHttpRequest());
            } else if (window.ActiveXObject) {
                // In Internet Explorer check for ActiveX versions of the XHR object.
                try {
                    return callback(null, new ActiveXObject("Msxml2.XMLHTTP"));
                } catch (e) {
                    return callback(null, new ActiveXObject("Microsoft.XMLHTTP"));
                }
            }
    
            // If no XHR support was found, throw an error.
            return callback(new Error());
        };
    
        var encodeUsingUrlEncoding = function (data) {
            if(typeof data === 'string') {
                return data;
            }
    
            var result = [];
            for(var dataItem in data) {
                if(data.hasOwnProperty(dataItem)) {
                    result.push(encodeURIComponent(dataItem) + '=' + encodeURIComponent(data[dataItem]));
                }
            }
    
            return result.join('&');
        };
    
        var utf8 = function (text) {
            text = text.replace(/\r\n/g, '\n');
            var result = '';
    
            for(var i = 0; i < text.length; i++) {
                var c = text.charCodeAt(i);
    
                if(c < 128) {
                        result += String.fromCharCode(c);
                } else if((c > 127) && (c < 2048)) {
                        result += String.fromCharCode((c >> 6) | 192);
                        result += String.fromCharCode((c & 63) | 128);
                } else {
                        result += String.fromCharCode((c >> 12) | 224);
                        result += String.fromCharCode(((c >> 6) & 63) | 128);
                        result += String.fromCharCode((c & 63) | 128);
                }
            }
    
            return result;
        };
    
        var base64 = function (text) {
            var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
    
            text = utf8(text);
            var result = '',
                    chr1, chr2, chr3,
                    enc1, enc2, enc3, enc4,
                    i = 0;
    
            do {
                chr1 = text.charCodeAt(i++);
                chr2 = text.charCodeAt(i++);
                chr3 = text.charCodeAt(i++);
    
                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;
    
                if(isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if(isNaN(chr3)) {
                    enc4 = 64;
                }
    
                result +=
                    keyStr.charAt(enc1) +
                    keyStr.charAt(enc2) +
                    keyStr.charAt(enc3) +
                    keyStr.charAt(enc4);
                chr1 = chr2 = chr3 = '';
                enc1 = enc2 = enc3 = enc4 = '';
            } while(i < text.length);
    
            return result;
        };
    
        var mergeHeaders = function () {
            // Use the first header object as base.
            var result = arguments[0];
    
            // Iterate through the remaining header objects and add them.
            for(var i = 1; i < arguments.length; i++) {
                var currentHeaders = arguments[i];
                for(var header in currentHeaders) {
                    if(currentHeaders.hasOwnProperty(header)) {
                        result[header] = currentHeaders[header];
                    }
                }
            }
    
            // Return the merged headers.
            return result;
        };
    
        var ajax = function (method, url, options, callback) {
            // Adjust parameters.
            if(typeof options === 'function') {
                callback = options;
                options = {};
            }
    
            // Set default parameter values.
            options.cache = options.cache || false;
            options.data = options.data || {};
            options.headers = options.headers || {};
            options.jsonp = options.jsonp || false;
            options.async = options.async === undefined ? true : options.async;
    
            // Merge the various header objects.
            var headers = mergeHeaders({
                'accept': '*/*',
                'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
            }, ajax.headers, options.headers);
    
            // Encode the data according to the content-type.
            var payload;
            if (headers['content-type'] === 'application/json') {
                payload = JSON.stringify(options.data);
            } else {
                payload = encodeUsingUrlEncoding(options.data);
            }
    
            // Specially prepare GET requests: Setup the query string, handle caching and make a JSONP call
            // if neccessary.
            if(method === 'GET') {
                // Setup the query string.
                var queryString = [];
                if(payload) {
                    queryString.push(payload);
                    payload = null;
                }
    
                // Handle caching.
                if(!options.cache) {
                    queryString.push('_=' + (new Date()).getTime());
                }
    
                // If neccessary prepare the query string for a JSONP call.
                if(options.jsonp) {
                    queryString.push('callback=' + options.jsonp);
                    queryString.push('jsonp=' + options.jsonp);
                }
    
                // Merge the query string and attach it to the url.
                queryString = queryString.join('&');
                if (queryString.length > 1) {
                    if (url.indexOf('?') > -1) {
                        url += '&' + queryString;
                    } else {
                        url += '?' + queryString;
                    }
                }
    
                // Make a JSONP call if neccessary.
                if(options.jsonp) {
                    var head = document.getElementsByTagName('head')[0];
                    var script = document.createElement('script');
                    script.type = 'text/javascript';
                    script.src = url;
                    head.appendChild(script);
                    return;
                }
            }
    
            // Since we got here, it is no JSONP request, so make a normal XHR request.
            getXhr(function (err, xhr) {
                if(err) return callback(err);
    
                // Open the request.
                xhr.open(method, url, options.async);
    
                // Set the request headers.
                for(var header in headers) {
                    if(headers.hasOwnProperty(header)) {
                        xhr.setRequestHeader(header, headers[header]);
                    }
                }
    
                // Handle the request events.
                xhr.onreadystatechange = function () {
                    if(xhr.readyState === 4) {
                        var data = xhr.responseText || '';
    
                        // If no callback is given, return.
                        if(!callback) {
                            return;
                        }
    
                        // Return an object that provides access to the data as text and JSON.
                        callback(xhr.status, {
                            text: function () {
                                return data;
                            },
    
                            json: function () {
                                return JSON.parse(data);
                            }
                        });
                    }
                };
    
                // Actually send the XHR request.
                xhr.send(payload);
            });
        };
    
        // Define the external interface.
        var http = {
            authBasic: function (username, password) {
                ajax.headers['Authorization'] = 'Basic ' + base64(username + ':' + password);
            },
    
            connect: function (url, options, callback) {
                return ajax('CONNECT', url, options, callback);
            },
    
            del: function (url, options, callback) {
                return ajax('DELETE', url, options, callback);
            },
    
            get: function (url, options, callback) {
                return ajax('GET', url, options, callback);
            },
    
            head: function (url, options, callback) {
                return ajax('HEAD', url, options, callback);
            },
    
            headers: function (headers) {
                ajax.headers = headers || {};
            },
    
            isAllowed: function (url, verb, callback) {
                this.options(url, function (status, data) {
                    callback(data.text().indexOf(verb) !== -1);
                });
            },
    
            options: function (url, options, callback) {
                return ajax('OPTIONS', url, options, callback);
            },
    
            patch: function (url, options, callback) {
                return ajax('PATCH', url, options, callback);
            },
    
            post: function (url, options, callback) {
                return ajax('POST', url, options, callback);
            },
    
            put: function (url, options, callback) {
                return ajax('PUT', url, options, callback);
            },
    
            trace: function (url, options, callback) {
                return ajax('TRACE', url, options, callback);
            }
        };
    
    
        var methode = options.type ? options.type.toLowerCase() : 'get';
    
        http[methode](options.url, options, function (status, data) {
            if (status === 200) {
                options.success(data.json(), status, null);
            } else {
                options.error(data.text(), status, null);
            }
        });
    }
    
    var _cookie = {
        create: function(name,value,minutes,domain) {
            var expires;
            if (minutes) {
                var date = new Date();
                date.setTime(date.getTime()+(minutes*60*1000));
                expires = "; expires="+date.toGMTString();
            }
            else expires = "";
            domain = (domain)? "domain="+domain+";" : "";
            document.cookie = name+"="+value+expires+";"+domain+"path=/";
        },
    
        read: function(name) {
            var nameEQ = name + "=";
            var ca = document.cookie.split(';');
            for(var i=0;i < ca.length;i++) {
                var c = ca[i];
                while (c.charAt(0)==' ') c = c.substring(1,c.length);
                if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
            }
            return null;
        },
    
        remove: function(name) {
            this.create(name,"",-1);
        }
    };
    
    var cookie_noop = {
        create: function(name,value,minutes,domain) {},
        read: function(name) { return null; },
        remove: function(name) {}
    };
    
    
    
    // move dependent functions to a container so that
    // they can be overriden easier in no jquery environment (node.js)
    var f = {
        extend: $ ? $.extend : _extend,
        each: $ ? $.each : _each,
        ajax: $ ? $.ajax : (typeof document !== 'undefined' ? _ajax : function() {}),
        cookie: typeof document !== 'undefined' ? _cookie : cookie_noop,
        detectLanguage: detectLanguage,
        escape: _escape,
        log: function(str) {
            if (o.debug && typeof console !== "undefined") console.log(str);
        },
        toLanguages: function(lng) {
            var languages = [];
            if (typeof lng === 'string' && lng.indexOf('-') > -1) {
                var parts = lng.split('-');
    
                lng = o.lowerCaseLng ?
                    parts[0].toLowerCase() +  '-' + parts[1].toLowerCase() :
                    parts[0].toLowerCase() +  '-' + parts[1].toUpperCase();
    
                if (o.load !== 'unspecific') languages.push(lng);
                if (o.load !== 'current') languages.push(parts[0]);
            } else {
                languages.push(lng);
            }
    
            for (var i = 0; i < o.fallbackLng.length; i++) {
                if (languages.indexOf(o.fallbackLng[i]) === -1 && o.fallbackLng[i]) languages.push(o.fallbackLng[i]);
            }
    
            return languages;
        },
        regexEscape: function(str) {
            return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
        }
    };
    function init(options, cb) {
        
        if (typeof options === 'function') {
            cb = options;
            options = {};
        }
        options = options || {};
        
        // override defaults with passed in options
        f.extend(o, options);
        delete o.fixLng; /* passed in each time */
    
        // create namespace object if namespace is passed in as string
        if (typeof o.ns == 'string') {
            o.ns = { namespaces: [o.ns], defaultNs: o.ns};
        }
    
        // fallback namespaces
        if (typeof o.fallbackNS == 'string') {
            o.fallbackNS = [o.fallbackNS];
        }
    
        // fallback languages
        if (typeof o.fallbackLng == 'string' || typeof o.fallbackLng == 'boolean') {
            o.fallbackLng = [o.fallbackLng];
        }
    
        // escape prefix/suffix
        o.interpolationPrefixEscaped = f.regexEscape(o.interpolationPrefix);
        o.interpolationSuffixEscaped = f.regexEscape(o.interpolationSuffix);
    
        if (!o.lng) o.lng = f.detectLanguage(); 
        if (o.lng) {
            // set cookie with lng set (as detectLanguage will set cookie on need)
            if (o.useCookie) f.cookie.create(o.cookieName, o.lng, o.cookieExpirationTime, o.cookieDomain);
        } else {
            o.lng =  o.fallbackLng[0];
            if (o.useCookie) f.cookie.remove(o.cookieName);
        }
    
        languages = f.toLanguages(o.lng);
        currentLng = languages[0];
        f.log('currentLng set to: ' + currentLng);
    
        var lngTranslate = translate;
        if (options.fixLng) {
            lngTranslate = function(key, options) {
                options = options || {};
                options.lng = options.lng || lngTranslate.lng;
                return translate(key, options);
            };
            lngTranslate.lng = currentLng;
        }
    
        pluralExtensions.setCurrentLng(currentLng);
    
        // add JQuery extensions
        if ($ && o.setJqueryExt) addJqueryFunct();
    
        // jQuery deferred
        var deferred;
        if ($ && $.Deferred) {
            deferred = $.Deferred();
        }
    
        // return immidiatly if res are passed in
        if (o.resStore) {
            resStore = o.resStore;
            initialized = true;
            if (cb) cb(lngTranslate);
            if (deferred) deferred.resolve(lngTranslate);
            if (deferred) return deferred.promise();
            return;
        }
    
        // languages to load
        var lngsToLoad = f.toLanguages(o.lng);
        if (typeof o.preload === 'string') o.preload = [o.preload];
        for (var i = 0, l = o.preload.length; i < l; i++) {
            var pres = f.toLanguages(o.preload[i]);
            for (var y = 0, len = pres.length; y < len; y++) {
                if (lngsToLoad.indexOf(pres[y]) < 0) {
                    lngsToLoad.push(pres[y]);
                }
            }
        }
    
        // else load them
        i18n.sync.load(lngsToLoad, o, function(err, store) {
            resStore = store;
            initialized = true;
    
            if (cb) cb(lngTranslate);
            if (deferred) deferred.resolve(lngTranslate);
        });
    
        if (deferred) return deferred.promise();
    }
    function preload(lngs, cb) {
        if (typeof lngs === 'string') lngs = [lngs];
        for (var i = 0, l = lngs.length; i < l; i++) {
            if (o.preload.indexOf(lngs[i]) < 0) {
                o.preload.push(lngs[i]);
            }
        }
        return init(cb);
    }
    
    function addResourceBundle(lng, ns, resources) {
        if (typeof ns !== 'string') {
            resources = ns;
            ns = o.ns.defaultNs;
        } else if (o.ns.namespaces.indexOf(ns) < 0) {
            o.ns.namespaces.push(ns);
        }
    
        resStore[lng] = resStore[lng] || {};
        resStore[lng][ns] = resStore[lng][ns] || {};
    
        f.extend(resStore[lng][ns], resources);
    }
    
    function removeResourceBundle(lng, ns) {
        if (typeof ns !== 'string') {
            ns = o.ns.defaultNs;
        }
    
        resStore[lng] = resStore[lng] || {};
        resStore[lng][ns] = {};
    }
    
    function setDefaultNamespace(ns) {
        o.ns.defaultNs = ns;
    }
    
    function loadNamespace(namespace, cb) {
        loadNamespaces([namespace], cb);
    }
    
    function loadNamespaces(namespaces, cb) {
        var opts = {
            dynamicLoad: o.dynamicLoad,
            resGetPath: o.resGetPath,
            getAsync: o.getAsync,
            customLoad: o.customLoad,
            ns: { namespaces: namespaces, defaultNs: ''} /* new namespaces to load */
        };
    
        // languages to load
        var lngsToLoad = f.toLanguages(o.lng);
        if (typeof o.preload === 'string') o.preload = [o.preload];
        for (var i = 0, l = o.preload.length; i < l; i++) {
            var pres = f.toLanguages(o.preload[i]);
            for (var y = 0, len = pres.length; y < len; y++) {
                if (lngsToLoad.indexOf(pres[y]) < 0) {
                    lngsToLoad.push(pres[y]);
                }
            }
        }
    
        // check if we have to load
        var lngNeedLoad = [];
        for (var a = 0, lenA = lngsToLoad.length; a < lenA; a++) {
            var needLoad = false;
            var resSet = resStore[lngsToLoad[a]];
            if (resSet) {
                for (var b = 0, lenB = namespaces.length; b < lenB; b++) {
                    if (!resSet[namespaces[b]]) needLoad = true;
                }
            } else {
                needLoad = true;
            }
    
            if (needLoad) lngNeedLoad.push(lngsToLoad[a]);
        }
    
        if (lngNeedLoad.length) {
            i18n.sync._fetch(lngNeedLoad, opts, function(err, store) {
                var todo = namespaces.length * lngNeedLoad.length;
    
                // load each file individual
                f.each(namespaces, function(nsIndex, nsValue) {
    
                    // append namespace to namespace array
                    if (o.ns.namespaces.indexOf(nsValue) < 0) {
                        o.ns.namespaces.push(nsValue);
                    }
    
                    f.each(lngNeedLoad, function(lngIndex, lngValue) {
                        resStore[lngValue] = resStore[lngValue] || {};
                        resStore[lngValue][nsValue] = store[lngValue][nsValue];
    
                        todo--; // wait for all done befor callback
                        if (todo === 0 && cb) {
                            if (o.useLocalStorage) i18n.sync._storeLocal(resStore);
                            cb();
                        }
                    });
                });
            });
        } else {
            if (cb) cb();
        }
    }
    
    function setLng(lng, options, cb) {
        if (typeof options === 'function') {
            cb = options;
            options = {};
        } else if (!options) {
            options = {};
        }
    
        options.lng = lng;
        return init(options, cb);
    }
    
    function lng() {
        return currentLng;
    }
    function addJqueryFunct() {
        // $.t shortcut
        $.t = $.t || translate;
    
        function parse(ele, key, options) {
            if (key.length === 0) return;
    
            var attr = 'text';
    
            if (key.indexOf('[') === 0) {
                var parts = key.split(']');
                key = parts[1];
                attr = parts[0].substr(1, parts[0].length-1);
            }
    
            if (key.indexOf(';') === key.length-1) {
                key = key.substr(0, key.length-2);
            }
    
            var optionsToUse;
            if (attr === 'html') {
                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options;
                ele.html($.t(key, optionsToUse));
            } else if (attr === 'text') {
                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.text() }, options) : options;
                ele.text($.t(key, optionsToUse));
            } else if (attr === 'prepend') {
                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options;
                ele.prepend($.t(key, optionsToUse));
            } else if (attr === 'append') {
                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options;
                ele.append($.t(key, optionsToUse));
            } else if (attr.indexOf("data-") === 0) {
                var dataAttr = attr.substr(("data-").length);
                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.data(dataAttr) }, options) : options;
                var translated = $.t(key, optionsToUse);
                //we change into the data cache
                ele.data(dataAttr, translated);
                //we change into the dom
                ele.attr(attr, translated);
            } else {
                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.attr(attr) }, options) : options;
                ele.attr(attr, $.t(key, optionsToUse));
            }
        }
    
        function localize(ele, options) {
            var key = ele.attr(o.selectorAttr);
            if (!key && typeof key !== 'undefined' && key !== false) key = ele.text() || ele.val();
            if (!key) return;
    
            var target = ele
              , targetSelector = ele.data("i18n-target");
            if (targetSelector) {
                target = ele.find(targetSelector) || ele;
            }
    
            if (!options && o.useDataAttrOptions === true) {
                options = ele.data("i18n-options");
            }
            options = options || {};
    
            if (key.indexOf(';') >= 0) {
                var keys = key.split(';');
    
                $.each(keys, function(m, k) {
                    if (k !== '') parse(target, k, options);
                });
    
            } else {
                parse(target, key, options);
            }
    
            if (o.useDataAttrOptions === true) ele.data("i18n-options", options);
        }
    
        // fn
        $.fn.i18n = function (options) {
            return this.each(function() {
                // localize element itself
                localize($(this), options);
    
                // localize childs
                var elements =  $(this).find('[' + o.selectorAttr + ']');
                elements.each(function() { 
                    localize($(this), options);
                });
            });
        };
    }
    function applyReplacement(str, replacementHash, nestedKey, options) {
        if (!str) return str;
    
        options = options || replacementHash; // first call uses replacement hash combined with options
        if (str.indexOf(options.interpolationPrefix || o.interpolationPrefix) < 0) return str;
    
        var prefix = options.interpolationPrefix ? f.regexEscape(options.interpolationPrefix) : o.interpolationPrefixEscaped
          , suffix = options.interpolationSuffix ? f.regexEscape(options.interpolationSuffix) : o.interpolationSuffixEscaped
          , unEscapingSuffix = 'HTML'+suffix;
    
        f.each(replacementHash, function(key, value) {
            var nextKey = nestedKey ? nestedKey + o.keyseparator + key : key;
            if (typeof value === 'object' && value !== null) {
                str = applyReplacement(str, value, nextKey, options);
            } else {
                if (options.escapeInterpolation || o.escapeInterpolation) {
                    str = str.replace(new RegExp([prefix, nextKey, unEscapingSuffix].join(''), 'g'), value);
                    str = str.replace(new RegExp([prefix, nextKey, suffix].join(''), 'g'), f.escape(value));
                } else {
                    str = str.replace(new RegExp([prefix, nextKey, suffix].join(''), 'g'), value);
                }
                // str = options.escapeInterpolation;
            }
        });
        return str;
    }
    
    // append it to functions
    f.applyReplacement = applyReplacement;
    
    function applyReuse(translated, options) {
        var comma = ',';
        var options_open = '{';
        var options_close = '}';
    
        var opts = f.extend({}, options);
        delete opts.postProcess;
    
        while (translated.indexOf(o.reusePrefix) != -1) {
            replacementCounter++;
            if (replacementCounter > o.maxRecursion) { break; } // safety net for too much recursion
            var index_of_opening = translated.lastIndexOf(o.reusePrefix);
            var index_of_end_of_closing = translated.indexOf(o.reuseSuffix, index_of_opening) + o.reuseSuffix.length;
            var token = translated.substring(index_of_opening, index_of_end_of_closing);
            var token_without_symbols = token.replace(o.reusePrefix, '').replace(o.reuseSuffix, '');
    
    
            if (token_without_symbols.indexOf(comma) != -1) {
                var index_of_token_end_of_closing = token_without_symbols.indexOf(comma);
                if (token_without_symbols.indexOf(options_open, index_of_token_end_of_closing) != -1 && token_without_symbols.indexOf(options_close, index_of_token_end_of_closing) != -1) {
                    var index_of_opts_opening = token_without_symbols.indexOf(options_open, index_of_token_end_of_closing);
                    var index_of_opts_end_of_closing = token_without_symbols.indexOf(options_close, index_of_opts_opening) + options_close.length;
                    try {
                        opts = f.extend(opts, JSON.parse(token_without_symbols.substring(index_of_opts_opening, index_of_opts_end_of_closing)));
                        token_without_symbols = token_without_symbols.substring(0, index_of_token_end_of_closing);
                    } catch (e) {
                    }
                }
            }
    
            var translated_token = _translate(token_without_symbols, opts);
            translated = translated.replace(token, translated_token);
        }
        return translated;
    }
    
    function hasContext(options) {
        return (options.context && (typeof options.context == 'string' || typeof options.context == 'number'));
    }
    
    function needsPlural(options) {
        return (options.count !== undefined && typeof options.count != 'string' && options.count !== 1);
    }
    
    function exists(key, options) {
        options = options || {};
    
        var notFound = _getDefaultValue(key, options)
            , found = _find(key, options);
    
        return found !== undefined || found === notFound;
    }
    
    function translate(key, options) {
        options = options || {};
    
        if (!initialized) {
            f.log('i18next not finished initialization. you might have called t function before loading resources finished.')
            return options.defaultValue || '';
        };
        replacementCounter = 0;
        return _translate.apply(null, arguments);
    }
    
    function _getDefaultValue(key, options) {
        return (options.defaultValue !== undefined) ? options.defaultValue : key;
    }
    
    function _injectSprintfProcessor() {
    
        var values = [];
    
        // mh: build array from second argument onwards
        for (var i = 1; i < arguments.length; i++) {
            values.push(arguments[i]);
        }
    
        return {
            postProcess: 'sprintf',
            sprintf:     values
        };
    }
    
    function _translate(potentialKeys, options) {
        if (options && typeof options !== 'object') {
            if (o.shortcutFunction === 'sprintf') {
                // mh: gettext like sprintf syntax found, automatically create sprintf processor
                options = _injectSprintfProcessor.apply(null, arguments);
            } else if (o.shortcutFunction === 'defaultValue') {
                options = {
                    defaultValue: options
                }
            }
        } else {
            options = options || {};
        }
    
        if (potentialKeys === undefined || potentialKeys === null) return '';
    
        if (typeof potentialKeys == 'string') {
            potentialKeys = [potentialKeys];
        }
    
        var key = potentialKeys[0];
    
        if (potentialKeys.length > 1) {
            for (var i = 0; i < potentialKeys.length; i++) {
                key = potentialKeys[i];
                if (exists(key, options)) {
                    break;
                }
            }
        }
    
        var notFound = _getDefaultValue(key, options)
            , found = _find(key, options)
            , lngs = options.lng ? f.toLanguages(options.lng) : languages
            , ns = options.ns || o.ns.defaultNs
            , parts;
    
        // split ns and key
        if (key.indexOf(o.nsseparator) > -1) {
            parts = key.split(o.nsseparator);
            ns = parts[0];
            key = parts[1];
        }
    
        if (found === undefined && o.sendMissing) {
            if (options.lng) {
                sync.postMissing(lngs[0], ns, key, notFound, lngs);
            } else {
                sync.postMissing(o.lng, ns, key, notFound, lngs);
            }
        }
    
        var postProcessor = options.postProcess || o.postProcess;
        if (found !== undefined && postProcessor) {
            if (postProcessors[postProcessor]) {
                found = postProcessors[postProcessor](found, key, options);
            }
        }
    
        // process notFound if function exists
        var splitNotFound = notFound;
        if (notFound.indexOf(o.nsseparator) > -1) {
            parts = notFound.split(o.nsseparator);
            splitNotFound = parts[1];
        }
        if (splitNotFound === key && o.parseMissingKey) {
            notFound = o.parseMissingKey(notFound);
        }
    
        if (found === undefined) {
            notFound = applyReplacement(notFound, options);
            notFound = applyReuse(notFound, options);
    
            if (postProcessor && postProcessors[postProcessor]) {
                var val = _getDefaultValue(key, options);
                found = postProcessors[postProcessor](val, key, options);
            }
        }
    
        return (found !== undefined) ? found : notFound;
    }
    
    function _find(key, options) {
        options = options || {};
    
        var optionWithoutCount, translated
            , notFound = _getDefaultValue(key, options)
            , lngs = languages;
    
        if (!resStore) { return notFound; } // no resStore to translate from
    
        // CI mode
        if (lngs[0].toLowerCase() === 'cimode') return notFound;
    
        // passed in lng
        if (options.lng) {
            lngs = f.toLanguages(options.lng);
    
            if (!resStore[lngs[0]]) {
                var oldAsync = o.getAsync;
                o.getAsync = false;
    
                i18n.sync.load(lngs, o, function(err, store) {
                    f.extend(resStore, store);
                    o.getAsync = oldAsync;
                });
            }
        }
    
        var ns = options.ns || o.ns.defaultNs;
        if (key.indexOf(o.nsseparator) > -1) {
            var parts = key.split(o.nsseparator);
            ns = parts[0];
            key = parts[1];
        }
    
        if (hasContext(options)) {
            optionWithoutCount = f.extend({}, options);
            delete optionWithoutCount.context;
            optionWithoutCount.defaultValue = o.contextNotFound;
    
            var contextKey = ns + o.nsseparator + key + '_' + options.context;
    
            translated = translate(contextKey, optionWithoutCount);
            if (translated != o.contextNotFound) {
                return applyReplacement(translated, { context: options.context }); // apply replacement for context only
            } // else continue translation with original/nonContext key
        }
    
        if (needsPlural(options)) {
            optionWithoutCount = f.extend({}, options);
            delete optionWithoutCount.count;
            optionWithoutCount.defaultValue = o.pluralNotFound;
    
            var pluralKey = ns + o.nsseparator + key + o.pluralSuffix;
            var pluralExtension = pluralExtensions.get(lngs[0], options.count);
            if (pluralExtension >= 0) {
                pluralKey = pluralKey + '_' + pluralExtension;
            } else if (pluralExtension === 1) {
                pluralKey = ns + o.nsseparator + key; // singular
            }
    
            translated = translate(pluralKey, optionWithoutCount);
            if (translated != o.pluralNotFound) {
                return applyReplacement(translated, {
                    count: options.count,
                    interpolationPrefix: options.interpolationPrefix,
                    interpolationSuffix: options.interpolationSuffix
                }); // apply replacement for count only
            } // else continue translation with original/singular key
        }
    
        var found;
        var keys = key.split(o.keyseparator);
        for (var i = 0, len = lngs.length; i < len; i++ ) {
            if (found !== undefined) break;
    
            var l = lngs[i];
    
            var x = 0;
            var value = resStore[l] && resStore[l][ns];
            while (keys[x]) {
                value = value && value[keys[x]];
                x++;
            }
            if (value !== undefined) {
                var valueType = Object.prototype.toString.apply(value);
                if (typeof value === 'string') {
                    value = applyReplacement(value, options);
                    value = applyReuse(value, options);
                } else if (valueType === '[object Array]' && !o.returnObjectTrees && !options.returnObjectTrees) {
                    value = value.join('\n');
                    value = applyReplacement(value, options);
                    value = applyReuse(value, options);
                } else if (value === null && o.fallbackOnNull === true) {
                    value = undefined;
                } else if (value !== null) {
                    if (!o.returnObjectTrees && !options.returnObjectTrees) {
                        if (o.objectTreeKeyHandler && typeof o.objectTreeKeyHandler == 'function') {
                            value = o.objectTreeKeyHandler(key, value, l, ns, options);
                        } else {
                            value = 'key \'' + ns + ':' + key + ' (' + l + ')\' ' +
                                'returned an object instead of string.';
                            f.log(value);
                        }
                    } else if (valueType !== '[object Number]' && valueType !== '[object Function]' && valueType !== '[object RegExp]') {
                        var copy = (valueType === '[object Array]') ? [] : {}; // apply child translation on a copy
                        f.each(value, function(m) {
                            copy[m] = _translate(ns + o.nsseparator + key + o.keyseparator + m, options);
                        });
                        value = copy;
                    }
                }
    
                if (typeof value === 'string' && value.trim() === '' && o.fallbackOnEmpty === true)
                    value = undefined;
    
                found = value;
            }
        }
    
        if (found === undefined && !options.isFallbackLookup && (o.fallbackToDefaultNS === true || (o.fallbackNS && o.fallbackNS.length > 0))) {
            // set flag for fallback lookup - avoid recursion
            options.isFallbackLookup = true;
    
            if (o.fallbackNS.length) {
    
                for (var y = 0, lenY = o.fallbackNS.length; y < lenY; y++) {
                    found = _find(o.fallbackNS[y] + o.nsseparator + key, options);
    
                    if (found) {
                        /* compare value without namespace */
                        var foundValue = found.indexOf(o.nsseparator) > -1 ? found.split(o.nsseparator)[1] : found
                          , notFoundValue = notFound.indexOf(o.nsseparator) > -1 ? notFound.split(o.nsseparator)[1] : notFound;
    
                        if (foundValue !== notFoundValue) break;
                    }
                }
            } else {
                found = _find(key, options); // fallback to default NS
            }
        }
    
        return found;
    }
    function detectLanguage() {
        var detectedLng;
    
        // get from qs
        var qsParm = [];
        if (typeof window !== 'undefined') {
            (function() {
                var query = window.location.search.substring(1);
                var parms = query.split('&');
                for (var i=0; i<parms.length; i++) {
                    var pos = parms[i].indexOf('=');
                    if (pos > 0) {
                        var key = parms[i].substring(0,pos);
                        var val = parms[i].substring(pos+1);
                        qsParm[key] = val;
                    }
                }
            })();
            if (qsParm[o.detectLngQS]) {
                detectedLng = qsParm[o.detectLngQS];
            }
        }
    
        // get from cookie
        if (!detectedLng && typeof document !== 'undefined' && o.useCookie ) {
            var c = f.cookie.read(o.cookieName);
            if (c) detectedLng = c;
        }
    
        // get from navigator
        if (!detectedLng && typeof navigator !== 'undefined') {
            detectedLng =  (navigator.language) ? navigator.language : navigator.userLanguage;
        }
        
        return detectedLng;
    }
    var sync = {
    
        load: function(lngs, options, cb) {
            if (options.useLocalStorage) {
                sync._loadLocal(lngs, options, function(err, store) {
                    var missingLngs = [];
                    for (var i = 0, len = lngs.length; i < len; i++) {
                        if (!store[lngs[i]]) missingLngs.push(lngs[i]);
                    }
    
                    if (missingLngs.length > 0) {
                        sync._fetch(missingLngs, options, function(err, fetched) {
                            f.extend(store, fetched);
                            sync._storeLocal(fetched);
    
                            cb(null, store);
                        });
                    } else {
                        cb(null, store);
                    }
                });
            } else {
                sync._fetch(lngs, options, function(err, store){
                    cb(null, store);
                });
            }
        },
    
        _loadLocal: function(lngs, options, cb) {
            var store = {}
              , nowMS = new Date().getTime();
    
            if(window.localStorage) {
    
                var todo = lngs.length;
    
                f.each(lngs, function(key, lng) {
                    var local = window.localStorage.getItem('res_' + lng);
    
                    if (local) {
                        local = JSON.parse(local);
    
                        if (local.i18nStamp && local.i18nStamp + options.localStorageExpirationTime > nowMS) {
                            store[lng] = local;
                        }
                    }
    
                    todo--; // wait for all done befor callback
                    if (todo === 0) cb(null, store);
                });
            }
        },
    
        _storeLocal: function(store) {
            if(window.localStorage) {
                for (var m in store) {
                    store[m].i18nStamp = new Date().getTime();
                    window.localStorage.setItem('res_' + m, JSON.stringify(store[m]));
                }
            }
            return;
        },
    
        _fetch: function(lngs, options, cb) {
            var ns = options.ns
              , store = {};
            
            if (!options.dynamicLoad) {
                var todo = ns.namespaces.length * lngs.length
                  , errors;
    
                // load each file individual
                f.each(ns.namespaces, function(nsIndex, nsValue) {
                    f.each(lngs, function(lngIndex, lngValue) {
                        
                        // Call this once our translation has returned.
                        var loadComplete = function(err, data) {
                            if (err) {
                                errors = errors || [];
                                errors.push(err);
                            }
                            store[lngValue] = store[lngValue] || {};
                            store[lngValue][nsValue] = data;
    
                            todo--; // wait for all done befor callback
                            if (todo === 0) cb(errors, store);
                        };
                        
                        if(typeof options.customLoad == 'function'){
                            // Use the specified custom callback.
                            options.customLoad(lngValue, nsValue, options, loadComplete);
                        } else {
                            //~ // Use our inbuilt sync.
                            sync._fetchOne(lngValue, nsValue, options, loadComplete);
                        }
                    });
                });
            } else {
                // Call this once our translation has returned.
                var loadComplete = function(err, data) {
                    cb(null, data);
                };
    
                if(typeof options.customLoad == 'function'){
                    // Use the specified custom callback.
                    options.customLoad(lngs, ns.namespaces, options, loadComplete);
                } else {
                    var url = applyReplacement(options.resGetPath, { lng: lngs.join('+'), ns: ns.namespaces.join('+') });
                    // load all needed stuff once
                    f.ajax({
                        url: url,
                        success: function(data, status, xhr) {
                            f.log('loaded: ' + url);
                            loadComplete(null, data);
                        },
                        error : function(xhr, status, error) {
                            f.log('failed loading: ' + url);
                            loadComplete('failed loading resource.json error: ' + error);
                        },
                        dataType: "json",
                        async : options.getAsync
                    });
                }    
            }
        },
    
        _fetchOne: function(lng, ns, options, done) {
            var url = applyReplacement(options.resGetPath, { lng: lng, ns: ns });
            f.ajax({
                url: url,
                success: function(data, status, xhr) {
                    f.log('loaded: ' + url);
                    done(null, data);
                },
                error : function(xhr, status, error) {
                    if ((status && status == 200) || (xhr && xhr.status && xhr.status == 200)) {
                        // file loaded but invalid json, stop waste time !
                        f.log('There is a typo in: ' + url);
                    } else if ((status && status == 404) || (xhr && xhr.status && xhr.status == 404)) {
                        f.log('Does not exist: ' + url);
                    } else {
                        var theStatus = status ? status : ((xhr && xhr.status) ? xhr.status : null);
                        f.log(theStatus + ' when loading ' + url);
                    }
                    
                    done(error, {});
                },
                dataType: "json",
                async : options.getAsync
            });
        },
    
        postMissing: function(lng, ns, key, defaultValue, lngs) {
            var payload = {};
            payload[key] = defaultValue;
    
            var urls = [];
    
            if (o.sendMissingTo === 'fallback' && o.fallbackLng[0] !== false) {
                for (var i = 0; i < o.fallbackLng.length; i++) {
                    urls.push({lng: o.fallbackLng[i], url: applyReplacement(o.resPostPath, { lng: o.fallbackLng[i], ns: ns })});
                }
            } else if (o.sendMissingTo === 'current' || (o.sendMissingTo === 'fallback' && o.fallbackLng[0] === false) ) {
                urls.push({lng: lng, url: applyReplacement(o.resPostPath, { lng: lng, ns: ns })});
            } else if (o.sendMissingTo === 'all') {
                for (var i = 0, l = lngs.length; i < l; i++) {
                    urls.push({lng: lngs[i], url: applyReplacement(o.resPostPath, { lng: lngs[i], ns: ns })});
                }
            }
    
            for (var y = 0, len = urls.length; y < len; y++) {
                var item = urls[y];
                f.ajax({
                    url: item.url,
                    type: o.sendType,
                    data: payload,
                    success: function(data, status, xhr) {
                        f.log('posted missing key \'' + key + '\' to: ' + item.url);
    
                        // add key to resStore
                        var keys = key.split('.');
                        var x = 0;
                        var value = resStore[item.lng][ns];
                        while (keys[x]) {
                            if (x === keys.length - 1) {
                                value = value[keys[x]] = defaultValue;
                            } else {
                                value = value[keys[x]] = value[keys[x]] || {};
                            }
                            x++;
                        }
                    },
                    error : function(xhr, status, error) {
                        f.log('failed posting missing key \'' + key + '\' to: ' + item.url);
                    },
                    dataType: "json",
                    async : o.postAsync
                });
            }
        }
    };
    // definition http://translate.sourceforge.net/wiki/l10n/pluralforms
    var pluralExtensions = {
    
        rules: {
            "ach": {
                "name": "Acholi", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "af": {
                "name": "Afrikaans", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ak": {
                "name": "Akan", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "am": {
                "name": "Amharic", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "an": {
                "name": "Aragonese", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ar": {
                "name": "Arabic", 
                "numbers": [
                    0, 
                    1, 
                    2, 
                    3, 
                    11, 
                    100
                ], 
                "plurals": function(n) { return Number(n===0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); }
            }, 
            "arn": {
                "name": "Mapudungun", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "ast": {
                "name": "Asturian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ay": {
                "name": "Aymar\u00e1", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "az": {
                "name": "Azerbaijani", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "be": {
                "name": "Belarusian", 
                "numbers": [
                    1, 
                    2, 
                    5
                ], 
                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
            }, 
            "bg": {
                "name": "Bulgarian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "bn": {
                "name": "Bengali", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "bo": {
                "name": "Tibetan", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "br": {
                "name": "Breton", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "bs": {
                "name": "Bosnian", 
                "numbers": [
                    1, 
                    2, 
                    5
                ], 
                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
            }, 
            "ca": {
                "name": "Catalan", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "cgg": {
                "name": "Chiga", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "cs": {
                "name": "Czech", 
                "numbers": [
                    1, 
                    2, 
                    5
                ], 
                "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); }
            }, 
            "csb": {
                "name": "Kashubian", 
                "numbers": [
                    1, 
                    2, 
                    5
                ], 
                "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
            }, 
            "cy": {
                "name": "Welsh", 
                "numbers": [
                    1, 
                    2, 
                    3, 
                    8
                ], 
                "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3); }
            }, 
            "da": {
                "name": "Danish", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "de": {
                "name": "German", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "dz": {
                "name": "Dzongkha", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "el": {
                "name": "Greek", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "en": {
                "name": "English", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "eo": {
                "name": "Esperanto", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "es": {
                "name": "Spanish", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "es_ar": {
                "name": "Argentinean Spanish", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "et": {
                "name": "Estonian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "eu": {
                "name": "Basque", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "fa": {
                "name": "Persian", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "fi": {
                "name": "Finnish", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "fil": {
                "name": "Filipino", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "fo": {
                "name": "Faroese", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "fr": {
                "name": "French", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "fur": {
                "name": "Friulian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "fy": {
                "name": "Frisian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ga": {
                "name": "Irish", 
                "numbers": [
                    1, 
                    2,
                    3,
                    7, 
                    11
                ], 
                "plurals": function(n) { return Number(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4) ;}
            }, 
            "gd": {
                "name": "Scottish Gaelic", 
                "numbers": [
                    1, 
                    2, 
                    3,
                    20
                ], 
                "plurals": function(n) { return Number((n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3); }
            }, 
            "gl": {
                "name": "Galician", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "gu": {
                "name": "Gujarati", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "gun": {
                "name": "Gun", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "ha": {
                "name": "Hausa", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "he": {
                "name": "Hebrew", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "hi": {
                "name": "Hindi", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "hr": {
                "name": "Croatian", 
                "numbers": [
                    1, 
                    2,
                    5
                ], 
                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
            }, 
            "hu": {
                "name": "Hungarian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "hy": {
                "name": "Armenian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ia": {
                "name": "Interlingua", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "id": {
                "name": "Indonesian", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "is": {
                "name": "Icelandic", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n%10!=1 || n%100==11); }
            }, 
            "it": {
                "name": "Italian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ja": {
                "name": "Japanese", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "jbo": {
                "name": "Lojban", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "jv": {
                "name": "Javanese", 
                "numbers": [
                    0, 
                    1
                ], 
                "plurals": function(n) { return Number(n !== 0); }
            }, 
            "ka": {
                "name": "Georgian", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "kk": {
                "name": "Kazakh", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "km": {
                "name": "Khmer", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "kn": {
                "name": "Kannada", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ko": {
                "name": "Korean", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "ku": {
                "name": "Kurdish", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "kw": {
                "name": "Cornish", 
                "numbers": [
                    1, 
                    2, 
                    3,
                    4
                ], 
                "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3); }
            }, 
            "ky": {
                "name": "Kyrgyz", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "lb": {
                "name": "Letzeburgesch", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ln": {
                "name": "Lingala", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "lo": {
                "name": "Lao", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "lt": {
                "name": "Lithuanian", 
                "numbers": [
                    1, 
                    2,
                    10
                ], 
                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); }
            }, 
            "lv": {
                "name": "Latvian", 
                "numbers": [
                    1, 
                    2, 
                    0
                ], 
                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n !== 0 ? 1 : 2); }
            }, 
            "mai": {
                "name": "Maithili", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "mfe": {
                "name": "Mauritian Creole", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "mg": {
                "name": "Malagasy", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "mi": {
                "name": "Maori", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "mk": {
                "name": "Macedonian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n==1 || n%10==1 ? 0 : 1); }
            }, 
            "ml": {
                "name": "Malayalam", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "mn": {
                "name": "Mongolian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "mnk": {
                "name": "Mandinka", 
                "numbers": [
                    0, 
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(0 ? 0 : n==1 ? 1 : 2); }
            }, 
            "mr": {
                "name": "Marathi", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ms": {
                "name": "Malay", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "mt": {
                "name": "Maltese", 
                "numbers": [
                    1, 
                    2, 
                    11, 
                    20
                ], 
                "plurals": function(n) { return Number(n==1 ? 0 : n===0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3); }
            }, 
            "nah": {
                "name": "Nahuatl", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "nap": {
                "name": "Neapolitan", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "nb": {
                "name": "Norwegian Bokmal", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ne": {
                "name": "Nepali", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "nl": {
                "name": "Dutch", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "nn": {
                "name": "Norwegian Nynorsk", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "no": {
                "name": "Norwegian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "nso": {
                "name": "Northern Sotho", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "oc": {
                "name": "Occitan", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "or": {
                "name": "Oriya", 
                "numbers": [
                    2, 
                    1
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "pa": {
                "name": "Punjabi", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "pap": {
                "name": "Papiamento", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "pl": {
                "name": "Polish", 
                "numbers": [
                    1, 
                    2,
                    5
                ], 
                "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
            }, 
            "pms": {
                "name": "Piemontese", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ps": {
                "name": "Pashto", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "pt": {
                "name": "Portuguese", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "pt_br": {
                "name": "Brazilian Portuguese", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "rm": {
                "name": "Romansh", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ro": {
                "name": "Romanian", 
                "numbers": [
                    1, 
                    2,
                    20
                ], 
                "plurals": function(n) { return Number(n==1 ? 0 : (n===0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2); }
            }, 
            "ru": {
                "name": "Russian", 
                "numbers": [
                    1, 
                    2, 
                    5
                ], 
                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
            }, 
            "sah": {
                "name": "Yakut", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "sco": {
                "name": "Scots", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "se": {
                "name": "Northern Sami", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "si": {
                "name": "Sinhala", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "sk": {
                "name": "Slovak", 
                "numbers": [
                    1, 
                    2, 
                    5
                ], 
                "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); }
            }, 
            "sl": {
                "name": "Slovenian", 
                "numbers": [
                    5, 
                    1, 
                    2, 
                    3
                ], 
                "plurals": function(n) { return Number(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); }
            }, 
            "so": {
                "name": "Somali", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "son": {
                "name": "Songhay", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "sq": {
                "name": "Albanian", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "sr": {
                "name": "Serbian", 
                "numbers": [
                    1, 
                    2,
                    5
                ], 
                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
            }, 
            "su": {
                "name": "Sundanese", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "sv": {
                "name": "Swedish", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "sw": {
                "name": "Swahili", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "ta": {
                "name": "Tamil", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "te": {
                "name": "Telugu", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "tg": {
                "name": "Tajik", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "th": {
                "name": "Thai", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "ti": {
                "name": "Tigrinya", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "tk": {
                "name": "Turkmen", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "tr": {
                "name": "Turkish", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "tt": {
                "name": "Tatar", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "ug": {
                "name": "Uyghur", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "uk": {
                "name": "Ukrainian", 
                "numbers": [
                    1, 
                    2,
                    5
                ], 
                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
            }, 
            "ur": {
                "name": "Urdu", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "uz": {
                "name": "Uzbek", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "vi": {
                "name": "Vietnamese", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "wa": {
                "name": "Walloon", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n > 1); }
            }, 
            "wo": {
                "name": "Wolof", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }, 
            "yo": {
                "name": "Yoruba", 
                "numbers": [
                    1, 
                    2
                ], 
                "plurals": function(n) { return Number(n != 1); }
            }, 
            "zh": {
                "name": "Chinese", 
                "numbers": [
                    1
                ], 
                "plurals": function(n) { return 0; }
            }
        },
    
        // for demonstration only sl and ar is added but you can add your own pluralExtensions
        addRule: function(lng, obj) {
            pluralExtensions.rules[lng] = obj;    
        },
    
        setCurrentLng: function(lng) {
            if (!pluralExtensions.currentRule || pluralExtensions.currentRule.lng !== lng) {
                var parts = lng.split('-');
    
                pluralExtensions.currentRule = {
                    lng: lng,
                    rule: pluralExtensions.rules[parts[0]]
                };
            }
        },
    
        get: function(lng, count) {
            var parts = lng.split('-');
    
            function getResult(l, c) {
                var ext;
                if (pluralExtensions.currentRule && pluralExtensions.currentRule.lng === lng) {
                    ext = pluralExtensions.currentRule.rule; 
                } else {
                    ext = pluralExtensions.rules[l];
                }
                if (ext) {
                    var i = ext.plurals(c);
                    var number = ext.numbers[i];
                    if (ext.numbers.length === 2 && ext.numbers[0] === 1) {
                        if (number === 2) { 
                            number = -1; // regular plural
                        } else if (number === 1) {
                            number = 1; // singular
                        }
                    }//console.log(count + '-' + number);
                    return number;
                } else {
                    return c === 1 ? '1' : '-1';
                }
            }
                        
            return getResult(parts[0], count);
        }
    
    };
    var postProcessors = {};
    var addPostProcessor = function(name, fc) {
        postProcessors[name] = fc;
    };
    // sprintf support
    var sprintf = (function() {
        function get_type(variable) {
            return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
        }
        function str_repeat(input, multiplier) {
            for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
            return output.join('');
        }
    
        var str_format = function() {
            if (!str_format.cache.hasOwnProperty(arguments[0])) {
                str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
            }
            return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
        };
    
        str_format.format = function(parse_tree, argv) {
            var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
            for (i = 0; i < tree_length; i++) {
                node_type = get_type(parse_tree[i]);
                if (node_type === 'string') {
                    output.push(parse_tree[i]);
                }
                else if (node_type === 'array') {
                    match = parse_tree[i]; // convenience purposes only
                    if (match[2]) { // keyword argument
                        arg = argv[cursor];
                        for (k = 0; k < match[2].length; k++) {
                            if (!arg.hasOwnProperty(match[2][k])) {
                                throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
                            }
                            arg = arg[match[2][k]];
                        }
                    }
                    else if (match[1]) { // positional argument (explicit)
                        arg = argv[match[1]];
                    }
                    else { // positional argument (implicit)
                        arg = argv[cursor++];
                    }
    
                    if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
                        throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
                    }
                    switch (match[8]) {
                        case 'b': arg = arg.toString(2); break;
                        case 'c': arg = String.fromCharCode(arg); break;
                        case 'd': arg = parseInt(arg, 10); break;
                        case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
                        case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
                        case 'o': arg = arg.toString(8); break;
                        case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
                        case 'u': arg = Math.abs(arg); break;
                        case 'x': arg = arg.toString(16); break;
                        case 'X': arg = arg.toString(16).toUpperCase(); break;
                    }
                    arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
                    pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
                    pad_length = match[6] - String(arg).length;
                    pad = match[6] ? str_repeat(pad_character, pad_length) : '';
                    output.push(match[5] ? arg + pad : pad + arg);
                }
            }
            return output.join('');
        };
    
        str_format.cache = {};
    
        str_format.parse = function(fmt) {
            var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
            while (_fmt) {
                if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
                    parse_tree.push(match[0]);
                }
                else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
                    parse_tree.push('%');
                }
                else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
                    if (match[2]) {
                        arg_names |= 1;
                        var field_list = [], replacement_field = match[2], field_match = [];
                        if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
                            field_list.push(field_match[1]);
                            while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
                                if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
                                    field_list.push(field_match[1]);
                                }
                                else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
                                    field_list.push(field_match[1]);
                                }
                                else {
                                    throw('[sprintf] huh?');
                                }
                            }
                        }
                        else {
                            throw('[sprintf] huh?');
                        }
                        match[2] = field_list;
                    }
                    else {
                        arg_names |= 2;
                    }
                    if (arg_names === 3) {
                        throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
                    }
                    parse_tree.push(match);
                }
                else {
                    throw('[sprintf] huh?');
                }
                _fmt = _fmt.substring(match[0].length);
            }
            return parse_tree;
        };
    
        return str_format;
    })();
    
    var vsprintf = function(fmt, argv) {
        argv.unshift(fmt);
        return sprintf.apply(null, argv);
    };
    
    addPostProcessor("sprintf", function(val, key, opts) {
        if (!opts.sprintf) return val;
    
        if (Object.prototype.toString.apply(opts.sprintf) === '[object Array]') {
            return vsprintf(val, opts.sprintf);
        } else if (typeof opts.sprintf === 'object') {
            return sprintf(val, opts.sprintf);
        }
    
        return val;
    });
    // public api interface
    i18n.init = init;
    i18n.setLng = setLng;
    i18n.preload = preload;
    i18n.addResourceBundle = addResourceBundle;
    i18n.removeResourceBundle = removeResourceBundle;
    i18n.loadNamespace = loadNamespace;
    i18n.loadNamespaces = loadNamespaces;
    i18n.setDefaultNamespace = setDefaultNamespace;
    i18n.t = translate;
    i18n.translate = translate;
    i18n.exists = exists;
    i18n.detectLanguage = f.detectLanguage;
    i18n.pluralExtensions = pluralExtensions;
    i18n.sync = sync;
    i18n.functions = f;
    i18n.lng = lng;
    i18n.addPostProcessor = addPostProcessor;
    i18n.options = o;
        
    return i18n; 

}));
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define('lab-grapher',e):"undefined"!=typeof window?window.LabGrapher=e():"undefined"!=typeof global?global.LabGrapher=e():"undefined"!=typeof self&&(self.LabGrapher=e())}(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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.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(require,module,exports){
module.exports.numberWidth = function(elem, cx, cy, fontSizeInPixels, numberStr) {
  var testSVG,
      testText,
      bbox,
      width,
      height,
      node;

  testSVG = elem.append("svg")
    .attr("width",  cx)
    .attr("height", cy)
    .attr("class", "graph");

  testText = testSVG.append('g')
    .append("text")
      .attr("class", "axis")
      .attr("x", -fontSizeInPixels/4 + "px")
      .attr("dy", ".35em")
      .attr("text-anchor", "end")
      .text(numberStr);

  node = testText.node();

  // This code is sometimes called by tests that use d3's jsdom-based mock SVG DOm, which
  // doesn't implement getBBox.
  if (node.getBBox) {
    bbox = testText.node().getBBox();
    width = bbox.width;
    height = bbox.height;
  } else {
    width = 0;
    height = 0;
  }

  testSVG.remove();
  return [width, height];
};

module.exports.axisProcessDrag = function(dragstart, currentdrag, domain) {
  var originExtent, maxDragIn,
      newdomain = domain,
      origin = 0,
      axis1 = domain[0],
      axis2 = domain[1],
      extent = axis2 - axis1;
  if (currentdrag !== 0) {
    if  ((axis1 >= 0) && (axis2 > axis1)) {                 // example: (20, 10, [0, 40]) => [0, 80]
      origin = axis1;
      originExtent = dragstart-origin;
      maxDragIn = originExtent * 0.4 + origin;
      if (currentdrag > maxDragIn) {
        change = originExtent / (currentdrag-origin);
        extent = axis2 - origin;
        newdomain = [axis1, axis1 + (extent * change)];
      }
    } else if ((axis1 < 0) && (axis2 > 0)) {                // example: (20, 10, [-40, 40])       => [-80, 80]
      origin = 0;                                           //          (-0.4, -0.2, [-1.0, 0.4]) => [-1.0, 0.4]
      originExtent = dragstart-origin;
      maxDragIn = originExtent * 0.4 + origin;
      if ((dragstart >= 0 && currentdrag > maxDragIn) || (dragstart  < 0  && currentdrag < maxDragIn)) {
        change = originExtent / (currentdrag-origin);
        newdomain = [axis1 * change, axis2 * change];
      }
    } else if ((axis1 < 0) && (axis2 < 0)) {                // example: (-60, -50, [-80, -40]) => [-120, -40]
      origin = axis2;
      originExtent = dragstart-origin;
      maxDragIn = originExtent * 0.4 + origin;
      if (currentdrag < maxDragIn) {
        change = originExtent / (currentdrag-origin);
        extent = axis1 - origin;
        newdomain = [axis2 + (extent * change), axis2];
      }
    }
  }
  newdomain[0] = +newdomain[0].toPrecision(5);
  newdomain[1] = +newdomain[1].toPrecision(5);
  return newdomain;
};

},{}],2:[function(require,module,exports){
var axis = require('./axis');
var i18n = require('./i18n');

module.exports = function Graph(idOrElement, options, message) {
  var api = {},   // Public API object to be returned.

      // D3 selection of the containing DOM element the graph is placed in
      elem,

      // Regular representation of containing DOM element the graph is placed in
      node,

      // JQuerified version of DOM element
      $node,

      // Size of containing DOM element
      cx, cy,

      // Calculated padding between edges of DOM container and interior plot area of graph.
      padding,

      // Object containing width and height in pixels of interior plot area of graph
      size,

      // D3 objects representing SVG elements/containers in graph
      svg,
      vis,
      plot,
      viewbox,
      title,
      xlabel,
      ylabel,
      selectedRulerX,
      selectedRulerY,

      // Strings used as tooltips when labels are visible but are truncated because
      // they are too big to be rendered into the space the graph allocates
      titleTooltip,

      // Instantiated D3 scale functions
      // currently either d3.scale.linear, d3.scale.log, or d3.scale.pow
      xScale,
      yScale,

      // The approximate number of gridlines in the plot, passed to d3.scale.ticks() function
      xTickCount,
      yTickCount,

      // Instantiated D3 line function: d3.svg.line()
      line,

      // numeric format functions wrapping the d3.format() functions
      fx,
      fy,

      // Instantiated D3 numeric format functions: d3.format()
      fx_d3,
      fy_d3,

      // Function for stroke styling of major and minor grid lines
      gridStroke = function(d) { return d ? "#ccc" : "#666"; },

      // Functions for translation of grid lines and associated numeric labels
      tx = function(d) { return "translate(" + xScale(d) + ",0)"; },
      ty = function(d) { return "translate(0," + yScale(d) + ")"; },

      // Div created and placed with z-index above all other graph layers that holds
      // graph action/mode buttons.
      buttonLayer,
      legendLayer,
      legendButton,
      selectionButton,
      drawButton,

      // div created above everything but the button layer for holding annotations
      annotationLayer,

      // Div created and placed with z-index under all other graph layers
      background,

      // Div created and placed with z-index under title.
      // It isn't styled by default, but it can be done by custom theme.
      titleBackground,

      // Optional string which can be displayed in background of interior plot area of graph.
      notification,

      // Optonal set of annotations that can be added dynamically to call out features of a graph
      annotations = [],

      // An array of strings holding 0 or more lines for the title of the graph
      titles = [],

      // D3 selection containing canvas
      graphCanvas,

      // HTML5 Canvas object containing just plotted lines
      gcanvas,
      gctx,
      canvasFillStyle = "rgba(255,255,255, 0.0)",

      // The style of the cursor when hovering over a sample.point marker.
      // The cursor changes depending on the operations that can be performed.
      markerCursorStyle,

      // Metrics calculated to support layout of titles, axes as
      // well as text and numeric labels for axes.
      fontSizeInPixels,
      halfFontSizeInPixels,
      quarterFontSizeInPixels,
      titleFontSizeInPixels,
      axisFontSizeInPixels,
      xlabelFontSizeInPixels,
      ylabelFontSizeInPixels,

      // Array objects containing width and height of X and Y axis labels
      xlabelMetrics,
      ylabelMetrics,

      // Width of widest numeric labels on X and Y axes
      xAxisNumberWidth,
      yAxisNumberWidth,

      // Height of numeric labels on X and Y axes
      xAxisNumberHeight,
      yAxisNumberHeight,

      // Padding necessary for X and Y axis labels to leave enough room for numeric labels
      xAxisVerticalPadding,
      yAxisHorizontalPadding,

      // Padding necessary between right side of interior plot and edge of graph so
      // make room for numeric lanel on right edge of X axis.
      xAxisLabelHorizontalPadding,

      // Baselines calculated for positioning of X and Y axis labels.
      xAxisLabelBaseline,
      yAxisLabelBaseline,

      // Thickness of draggable areas for rescaling axes, these surround numeric labels
      xAxisDraggableHeight,
      yAxisDraggableWidth,

      // D3 SVG rects used to implement axis dragging
      xAxisDraggable,
      yAxisDraggable,

      // Strings used as tooltips when numeric axis draggables are visible but responsive
      // layout system has removed the axis labels because of small size of graph.
      xAxisDraggableTooltip,
      yAxisDraggableTooltip,

      // Used to calculate styles for markers appearing on samples/points (normally circles)
      markerRadius,
      markerStrokeWidth,

      // Stroke width used for lines in graph
      lineWidth,

      // Used to categorize size of graphs in responsive layout mode where
      // certain graph chrome is removed when graph is rendered smaller.
      sizeType = {
        category: "medium",
        value: 3,
        icon: 120,
        tiny: 240,
        small: 480,
        medium: 960,
        large: 1920
      },

      // Padding of a title when it's placed on the left side.
      titleLeftPadding = "10px",

      // State variables indicating whether an axis drag operation is in place.
      // NaN values are used to indicate operation not in progress and
      // checked like this: if (!isNaN(downx)) { resacle operation in progress }
      //
      // When drag/rescale operation is occuring values contain plot
      // coordinates of start of drag (0 is a valid value).
      downx = NaN,
      downy = NaN,

      // State variable indicating whether a data point is being dragged.
      // When data point drag operation is occuring value contain two element
      // array wiith plot coordinates of drag position.
      draggedPoint = null,

      // When a data point is selected contains two element array wiith plot coordinates
      // of selected data point.
      selected = null,

      // An array of data points in the plot which are near the cursor.
      // Normally used to temporarily display data point markers when cursor
      // is nearby when markAllDataPoints is disabled.
      selectable = [],

      // An array containing two-element arrays consisting of X and Y values for samples/points
      points = [],

      // Consumers of points that have been added by user clicks
      pointListeners = [],

      // An array containing 1 or more points arrays to be plotted. Data is not indexed here
      // and sorted when "sortPoints" option is enabled.
      pointArray,

      // Keeps the same set of points like pointArray, but data is not sorted, but provides
      // indexing instead.
      pointArrayIndexed,

      // Current extent of points plotted by graph.
      pointsXMin,
      pointsXMax,
      pointsYMin,
      pointsYMax,

      // Index into points array for current sample/point.
      // Normally references data point last added.
      // Current sample can refer to earlier points. This is
      // represented in the view by using a desaturated styling for
      // plotted data after te currentSample.
      currentSample,

      // When graphing data samples as opposed to [x, y] data pairs contains
      // the fixed time interval between subsequent samples.
      sampleInterval,

      // Normally data sent to graph as samples starts at an X value of 0
      // A different starting x value can be set
      dataSampleStart,

      // The default options for a graph
      default_options = {
        // Enables the button layer with: AutoScale...
        showButtons: true,
        // "icons" or "text".
        buttonsStyle: "icons",
        // "vertical" (overlaps with the plotting area) or "horizontal" (right below the title).
        buttonsLayout: "vertical",

        // Whether or not to show the graph's legend
        legendVisible: false,

        // Responsive Layout provides progressive removal of
        // graph elements when size gets smaller
        responsiveLayout: false,

        // Font sizes for graphs are normally specified using ems.
        // When fontScaleRelativeToParent to true the font-size of the
        // containing element is set based on the size of the containing
        // element. hs means whn the containing element is smaller the
        // font-size of the labels in the graph will be smaller.
        fontScaleRelativeToParent: true,
        hideAxisValues: false,

        enableAutoScaleButton: true,
        enableAxisScaling: true,
        enableZooming: true,

        enableSelectionButton: false,
        clearSelectionOnLeavingSelectMode: false,

        enableDrawButton: false,
        enableLegendButton: true,

        titlePosition: "center", // or "left"

        drawIndex: 0,

        //
        // dataType can be either 'points or 'samples'
        //
        dataType: 'points',
        //
        // dataType: 'points'
        //
        // Arrays of two-element arrays of x, y data pairs, this is the internal
        // format the graphers uses to represent data.
        dataPoints:      [],
        //
        // dataType: 'samples'
        //
        // An array of samples (or an array or arrays of samples)
        dataSamples:     [],
        // The constant time interval between sample values
        sampleInterval:  1,
        // Normally data sent to graph as samples starts at an X value of 0
        // A different starting x value can be set
        dataSampleStart: 0,

        // If true then all points added to graph will be sorted by X coordinate.
        sortPoints:      true,

        // title can be a string or an array of strings, if an
        // array of strings each element is on a separate line.
        title:          "graph",

        // The labels for the axes, these are separate from the numeric labels.
        xlabel:         "x-axis",
        ylabel:         "y-axis",

        // Initial extent of the X and Y axes.
        xmax:            10,
        xmin:            0,
        ymax:            10,
        ymin:            0,

        // Auto-scaling of X axis when at least one point exceeds current domain.
        autoScaleX:       true,
        autoScaleY:       true,
        autoScalePadding: 0.3,

        // Approximate values for how many gridlines should appear on the axes.
        xTickCount:      10,
        yTickCount:      10,

        // The formatter strings used to convert numbers into strings.
        // see: https://github.com/mbostock/d3/wiki/Formatting#wiki-d3_format
        xFormatter:      ".3s",
        yFormatter:      ".3s",

        // Scale type: options are:
        //   linear: https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-linear
        //   log:    https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-log
        //   pow:    https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-pow
        xscale:         'linear',
        yscale:         'linear',

        // Used when scale type is set to "pow"
        xscaleExponent:  0.5,
        yscaleExponent:  0.5,

        // How many samples/points over which a graph shift should take place
        // when the data being plotted gets close to the edge of the X axis.
        axisShift:       10,

        // selectablePoints: false,

        // true if data points should be marked ... currently marked with a circle.
        markAllDataPoints:   false,

        // only show circles when hovering near them with the mouse or
        // tapping near then on a tablet
        markNearbyDataPoints: false,

        // number of circles to show on each side of the central point
        extraCirclesVisibleOnHover: 2,

        // true to show dashed horizontal and vertical rulers when a circle is selected
        showRulersOnSelection: false,

        // width of the line used for plotting
        lineWidth:      2.0,

        // Enable values of data points to be changed by selecting and dragging.
        dataChange:      false,

        // Enables adding of data to a graph by option/alt clicking in the graph.
        addData:         false,

        // Set value to a string and it will be rendered in background of graph.
        notification:    false,

        // Render lines between samples/points
        lines:           true,

        // Render vertical bars extending up to samples/points
        bars:            false,

        // Callback, called after autoscale button is clicked
        onAutoscale:     null,

        // Callack, called after X or Y axis is changed (due to any reason, e.g. manual or auto-scaling)
        onXDomainChange: null,
        onYDomainChange: null,

        // The R, G, and B values to be used to plot samples in each data channel. This default can
        // be overridden at construction time, but the caller must provide colors for each channel.
        // If there are n channels and m < n provided colors, the last n - m channels will be drawn
        // using the last color in the list
        dataColors: [
          "#a00000",     // channel 0   (red)
          "#2ca000",     // channel 1   (green-yellow)
          "#2c00a0"      // channels 2+ (blue-purple)
        ],

        // An array of strings to be paired with the data colors to build a legend
        legendLabels: []
      },

      // brush selection variables
      selection_region = {
        xmin: null,
        xmax: null,
        ymin: null,
        ymax: null
      },
      has_selection = false,
      selection_visible = false,
      selection_enabled = true,
      selection_listener,
      draw_enabled = false,
      brush_element,
      brush_control;


  // ------------------------------------------------------------
  //
  // Initialization
  //
  // ------------------------------------------------------------

  function initialize(idOrElement, opts, mesg) {
    if (opts || !options) {
      options = setupOptions(opts);
    }
    if (options.lang) {
      // Set language only if it's explicitly defined, don't use any default value.
      // Language can be also set by client library using global object:
      // LabGrapher.i18n.lang = 'es'
      // Default option could overwrite that.
      i18n.lang = options.lang;
    }

    initializeLayout(idOrElement, mesg);

    options.xrange = options.xmax - options.xmin;
    options.yrange = options.ymax - options.ymin;

    if (Object.prototype.toString.call(options.title) === "[object Array]") {
      titles = options.title;
    } else {
      titles = [options.title];
    }
    titles.reverse();

    // use local variables for both access speed and for responsive over-riding
    sampleInterval = options.sampleInterval;
    dataSampleStart = options.dataSampleStart;
    lineWidth = options.lineWidth;

    size = {
      "width":  120,
      "height": 120
    };

    setupScales();

    fx_d3 = d3.format(options.xFormatter);
    fy_d3 = d3.format(options.yFormatter);

    // Wrappers around certain d3 formatters to prevent problems like this:
    //   scale = d3.scale.linear().domain([-.7164, .7164])
    //   scale.ticks(10).map(d3.format('.3r'))
    //   => ["-0.600", "-0.400", "-0.200", "-0.0000000000000000888", "0.200", "0.400", "0.600"]

    fx = function(num) {
      var domain = xScale.domain(),
          onePercent = Math.abs((domain[1] - domain[0])*0.01);
      if (Math.abs(0+num) < onePercent) {
        num = 0;
      }
      return fx_d3(num);
    };

    fy = function(num) {
      var domain = yScale.domain(),
          onePercent = Math.abs((domain[1] - domain[0])*0.01);
      if (Math.abs(0+num) < onePercent) {
        num = 0;
      }
      return fy_d3(num);
    };

    xTickCount = options.xTickCount;
    yTickCount = options.yTickCount;

    pointsXMin = pointsYMin = Infinity;
    pointsXMax = pointsYMax = -Infinity;
    pointArray = [];
    switch(options.dataType) {
      case "fake":
      points = fakeDataPoints();
      pointArray = [points];
      break;

      case 'points':
      resetDataPoints(options.dataPoints);
      break;

      case 'samples':
      resetDataSamples(options.dataSamples, sampleInterval, dataSampleStart);
      break;
    }

    selectable = [];
    selected = null;

    setCurrentSample(points.length);
  }

  function initializeLayout(idOrElement, mesg) {
    if (idOrElement) {
      // d3.select works both for element ID (e.g. "#grapher")
      // and for DOM element.
      elem = d3.select(idOrElement);
      node = elem.node();
      $node = $(node);
      // cx = $node.width();
      // cy = $node.height();
      cx = elem.property("clientWidth");
      cy = elem.property("clientHeight");
    }

    if (mesg) {
      message = mesg;
    }

    if (svg !== undefined) {
      svg.remove();
      svg = undefined;
    }

    if (background !== undefined) {
      background.remove();
      background = undefined;
    }

    if (graphCanvas !== undefined) {
      graphCanvas.remove();
      graphCanvas = undefined;
    }

    if (options.dataChange) {
      markerCursorStyle = "ns-resize";
    } else {
      markerCursorStyle = "crosshair";
    }

    scale();

    // drag axis logic
    downx = NaN;
    downy = NaN;
    draggedPoint = null;
  }

  function scale(w, h) {
    if (!w && !h) {
      cx = Math.max(elem.property("clientWidth"), 60);
      cy = Math.max(elem.property("clientHeight"),60);
    } else {
      cx = w;
      node.style.width =  cx +"px";
      if (!h) {
        node.style.height = "100%";
        h = elem.property("clientHeight");
        cy = h;
        node.style.height = cy +"px";
      } else {
        cy = h;
        node.style.height = cy +"px";
      }
    }
    calculateSizeType();
  }

  function calculateLayout() {
    scale();

    fontSizeInPixels = parseFloat($node.css("font-size"));

    if (!options.fontScaleRelativeToParent) {
      $node.css("font-size", 0.5 + sizeType.value/6 + 'em');
    }

    fontSizeInPixels = parseFloat($node.css("font-size"));

    halfFontSizeInPixels = fontSizeInPixels/2;
    quarterFontSizeInPixels = fontSizeInPixels/4;

    if (svg === undefined) {
      titleFontSizeInPixels =  fontSizeInPixels;
      axisFontSizeInPixels =   fontSizeInPixels;
      xlabelFontSizeInPixels = fontSizeInPixels;
      ylabelFontSizeInPixels = fontSizeInPixels;
    } else {
      titleFontSizeInPixels =  parseFloat($("svg.graph text.title").css("font-size"));
      axisFontSizeInPixels =   parseFloat($("svg.graph text.axis").css("font-size"));
      xlabelFontSizeInPixels = parseFloat($("svg.graph text.xlabel").css("font-size"));
      ylabelFontSizeInPixels = parseFloat($("svg.graph text.ylabel").css("font-size"));
    }
    updateAxesAndSize();

    updateScales();

    line = d3.svg.line()
        .x(function(d, i) { return xScale(points[i][0]); })
        .y(function(d, i) { return yScale(points[i][1]); });
  }

  function setupOptions(options) {
    if (options) {
      for(var p in default_options) {
        if (options[p] === undefined) {
          options[p] = default_options[p];
        }
      }
    } else {
      options = default_options;
    }
    if (options.axisShift < 1) options.axisShift = 1;
    return options;
  }

  function getTopPadding() {
    var topPadding = fontSizeInPixels;
    if (options.title) {
      topPadding = titleFontSizeInPixels * 1.8;
    }
    if (options.buttonsLayout === "horizontal") {
      // Leave some space for buttons.
      topPadding += fontSizeInPixels * 1.3;
    }
    return topPadding;
  }

  function updateAxesAndSize() {
    xlabelMetrics = [fontSizeInPixels, fontSizeInPixels];
    ylabelMetrics = [fontSizeInPixels*2, fontSizeInPixels];
    if (xScale !== undefined) {
      // Find the widest X and Y axis labels, as those metrics are going to be used to calculate padding.
      xScale.ticks(xTickCount).forEach(function (tickVal) {
        var metrics = axis.numberWidth(elem, cx, cy, axisFontSizeInPixels, fx(tickVal));
        if (metrics[0] > xlabelMetrics[0]) { // metrics[0] - width, metrics[1] - height
          xlabelMetrics = metrics;
        }
      });
      yScale.ticks(yTickCount).forEach(function (tickVal) {
        var metrics = axis.numberWidth(elem, cx, cy, axisFontSizeInPixels, fy(tickVal));
        if (metrics[0] > ylabelMetrics[0]) { // metrics[0] - width, metrics[1] - height
          ylabelMetrics = metrics;
        }
      });
    }

    xAxisNumberWidth  = xlabelMetrics[0];
    xAxisNumberHeight = xlabelMetrics[1];

    xAxisLabelHorizontalPadding = xAxisNumberWidth * 0.6;
    xAxisDraggableHeight = xAxisNumberHeight * 1.1;
    xAxisVerticalPadding = xAxisDraggableHeight + xAxisNumberHeight*1.3;
    xAxisLabelBaseline = xAxisVerticalPadding-xAxisNumberHeight/3;

    yAxisNumberWidth  = ylabelMetrics[0];
    yAxisNumberHeight = ylabelMetrics[1];

    yAxisDraggableWidth    = yAxisNumberWidth + xAxisNumberHeight/4;
    yAxisHorizontalPadding = yAxisDraggableWidth + yAxisNumberHeight;
    yAxisLabelBaseline     = -(yAxisDraggableWidth+yAxisNumberHeight/4);
    if (options.hideAxisValues) {
      xAxisLabelBaseline = xAxisLabelBaseline - xAxisNumberHeight*1.3;
      yAxisLabelBaseline = -ylabelFontSizeInPixels/4;
    }

    switch(sizeType.value) {
      case 0:         // icon
      padding = {
        "top":    halfFontSizeInPixels,
        "right":  halfFontSizeInPixels,
        "bottom": xlabelFontSizeInPixels*1.25,
        "left":   ylabelFontSizeInPixels*1.25
      };
      break;

      case 1:         // tiny
      padding = {
        "top":    getTopPadding(),
        "right":  halfFontSizeInPixels,
        "bottom": xlabelFontSizeInPixels*1.25,
        "left":   ylabelFontSizeInPixels*1.25
      };
      break;

      case 2:         // small
      padding = {
        "top":    getTopPadding(),
        "right":  xAxisLabelHorizontalPadding,
        "bottom": options.hideAxisValues ? xlabelFontSizeInPixels*1.25 : axisFontSizeInPixels*1.25,
        "left": options.hideAxisValues ? ylabelFontSizeInPixels*1.25 : yAxisNumberWidth*1.25
      };
      xTickCount = Math.max(6, options.xTickCount/2);
      yTickCount = Math.max(6, options.yTickCount/2);
      break;

      case 3:         // medium
      padding = {
        "top":    getTopPadding(),
        "right":  xAxisLabelHorizontalPadding,
        "bottom": options.hideAxisValues ? xlabelFontSizeInPixels*1.25 : (options.xlabel ? xAxisVerticalPadding : axisFontSizeInPixels*1.25),
        "left": options.hideAxisValues ? ylabelFontSizeInPixels*1.25 : (options.ylabel ? yAxisHorizontalPadding : yAxisNumberWidth)
      };
      break;

      default:         // large
      padding = {
        "top":    getTopPadding(),
        "right":  xAxisLabelHorizontalPadding,
        "bottom": options.hideAxisValues ? xlabelFontSizeInPixels*1.25 : (options.xlabel ? xAxisVerticalPadding : axisFontSizeInPixels*1.25),
        "left": options.hideAxisValues ? ylabelFontSizeInPixels*1.25 : (options.ylabel ? yAxisHorizontalPadding : yAxisNumberWidth)
      };
      break;
    }

    if (sizeType.value > 2 ) {
      padding.top += (titles.length-1) * sizeType.value/3 * sizeType.value/3 * fontSizeInPixels;
    } else {
      titles = [titles[0]];
    }

    size.width  = Math.max(cx - padding.left - padding.right, 60);
    size.height = Math.max(cy - padding.top  - padding.bottom, 60);
  }

  function calculateSizeType() {
    if (options.responsiveLayout) {
      if (cx <= sizeType.icon) {
        sizeType.category = 'icon';
        sizeType.value = 0;
      } else if (cx <= sizeType.tiny) {
        sizeType.category = 'tiny';
        sizeType.value = 1;
      } else if (cx <= sizeType.small) {
        sizeType.category = 'small';
        sizeType.value = 2;
      } else if (cx <= sizeType.medium) {
        sizeType.category = 'medium';
        sizeType.value = 3;
      } else {
        sizeType.category = 'large';
        sizeType.value = 4;
      }
    } else {
      sizeType.category = 'large';
      sizeType.value = 4;
    }
  }

  // Setup xScale, yScale, making sure that options.xmax/xmin/ymax/ymin always reflect changes to
  // the relevant domains.
  function setupScales() {
    function domainObservingScale(scale, callback) {
      var domain = scale.domain;
      var nice = scale.nice;
      scale.domain = function() {
        var result = domain.apply(scale, arguments);
        if (arguments.length) {
          callback();
        }
        return result;
      };
      scale.nice = function() {
        var result = nice.apply(scale, arguments);
        callback();
        return result;
      };
      return scale;
    }

    xScale = domainObservingScale(d3.scale[options.xscale](), function() {
      options.xmin = xScale.domain()[0];
      options.xmax = xScale.domain()[1];
      if (options.onXDomainChange) {
        options.onXDomainChange.call(null, options.xmin, options.xmax);
      }
    });
    yScale = domainObservingScale(d3.scale[options.yscale](), function() {
      options.ymin = yScale.domain()[0];
      options.ymax = yScale.domain()[1];
      if (options.onYDomainChange) {
        options.onYDomainChange.call(null, options.ymin, options.ymax);
      }
    });
    updateScales();
  }

  function updateScales() {
    updateXScale();
    updateYScale();
  }

  // Update the x-scale.
  function updateXScale() {
    xScale.domain([options.xmin, options.xmax])
          .range([0, size.width]);
  }

  // Update the y-scale.
  function updateYScale() {
    yScale.domain([options.ymin, options.ymax])
          .range([size.height, 0]);
  }

  function fakeDataPoints() {
    var yrange2 = options.yrange / 2,
        yrange4 = yrange2 / 2,
        pnts;

    options.datacount = size.width/30;
    options.xtic = options.xrange / options.datacount;
    options.ytic = options.yrange / options.datacount;

    pnts = d3.range(options.datacount).map(function(i) {
      return [i * options.xtic + options.xmin, options.ymin + yrange4 + Math.random() * yrange2 ];
    });
    return pnts;
  }

  function setCurrentSample(samplePoint) {
    if (typeof samplePoint === "number") {
      currentSample = samplePoint;
    } else if (samplePoint === "last") {
      currentSample = 0;
      pointArray.forEach(function (arr) {
        if (arr.length > currentSample) {
          currentSample = arr.length ;
        }
      });
    }
    if (typeof currentSample !== "number") {
      currentSample = points.length-1;
    }
    return currentSample;
  }

  // converts data samples into an array of points
  function indexedData(samples, interval, start) {
    var i = 0,
        pnts = [];
    interval = interval || 1;
    start = start || 0;
    for (i = 0; i < samples.length;  i++) {
      pnts.push([i * interval + start, samples[i]]);
    }
    return pnts;
  }

  //
  // Update notification message
  //
  function notify(mesg) {
    message = mesg;
    if (mesg) {
      notification.text(mesg);
    } else {
      notification.text('');
    }
  }


  function createButtonLayer() {
    buttonLayer = elem.append("div");

    buttonLayer
      .attr("class", "button-layer")
      .style("z-index", 3);

    if (options.enableLegendButton && options.legendLabels.length > 0) {
      legendButton = buttonLayer.append('a');
      legendButton.attr({
            "class": "graph-button legend",
            "title": i18n.t("tooltips.legend")
          })
          .on("click", function() {
            toggleLegend();
          });
      if (options.buttonsStyle === "icons") {
        legendButton.append("i").attr("class", "icon-list-ul");
      } else {
        legendButton.text(i18n.t("labels.legend"));
      }
    }

    if (options.enableAutoScaleButton) {
      var autoscaleButton = buttonLayer.append('a');
      autoscaleButton.attr({
            "class": "graph-button autoscale",
            "title": i18n.t("tooltips.autoscale")
          })
          .on("click", function() {
            autoscale(true);
            redraw();
          });
      if (options.buttonsStyle === "icons") {
        autoscaleButton.append("i").attr("class", "icon-picture");
      } else {
        autoscaleButton.text(i18n.t("labels.autoscale"));
      }
    }

    if (options.enableSelectionButton) {
      selectionButton = buttonLayer.append('a');
      selectionButton.attr({
            "class": "graph-button selection",
            "title": i18n.t("tooltips.selection")
          })
          .on("click", function() {
            toggleSelection();
          });
      if (options.buttonsStyle === "icons") {
        selectionButton.append("i").attr("class", "icon-cut");
      } else {
        selectionButton.text(i18n.t("labels.selection"));
      }
    }

    if (options.enableDrawButton) {
      drawButton = buttonLayer.append('a');
      drawButton.attr({
            "class": "graph-button draw",
            "title": i18n.t("tooltips.draw")
          })
          .on("click", function() {
            toggleDraw();
          });
      if (options.buttonsStyle === "icons") {
        drawButton.append("i").attr("class", "icon-pencil");
      } else {
        drawButton.text(i18n.t("labels.draw"));
      }
    }

    resizeButtonLayer();
  }

  function resizeButtonLayer() {
    if (options.buttonsLayout === "vertical") {
      buttonLayer.style({
        "top":   padding.top + halfFontSizeInPixels * 0.5 + "px",
        "right": padding.right + halfFontSizeInPixels * 0.5 + "px"
      });
      buttonLayer.classed("horizontal", false);
    } else if (options.buttonsLayout === "horizontal") {
      buttonLayer.style({
        "top":  padding.top - fontSizeInPixels * 1.8 + "px",
        "width": (padding.left + size.width) + "px"
      });
      buttonLayer.classed("horizontal", true);
    }
  }
  function createLegendLayer() {
    var color = "black", item;
    legendLayer = elem.append("ul");

    legendLayer
      .attr("class", "legend-layer")
      .style("z-index", 3);

    for (var i = 0; i < options.legendLabels.length; i++) {
      if (options.dataColors.length > i) {
        color = options.dataColors[i];
      }
      item = legendLayer.append("li");
      item.append("div")
        .attr("class", "legend-colorsquare")
        .style("background-color", color);
      item.append("label")
        .text(options.legendLabels[i]);
    }
  }

  function createAnnotationLayer() {
    annotationLayer = elem.append("div");

    annotationLayer
      .attr("class", "annotation-layer")
      .style("z-index", 3);

    resizeAnnotationLayer();
  }

  function resizeAnnotationLayer() {
    annotationLayer
      .style({
        "width": size.width + "px",
        "height": size.height + "px",
        "top": padding.top + "px",
        "left": padding.left + "px"
      });
  }

  // ------------------------------------------------------------
  //
  // Rendering
  //
  // ------------------------------------------------------------

  //
  // Render a new graph by creating the SVG and Canvas elements
  //
  function renderNewGraph() {
    svg = elem.append("svg")
        .attr("width",  cx)
        .attr("height", cy)
        .attr("class", "graph")
        .style('z-index', 2);
        // .attr("tabindex", tabindex || 0);

    vis = svg.append("g")
        .attr("transform", "translate(" + padding.left + "," + padding.top + ")");

    plot = vis.append("rect")
      .attr("class", "plot")
      .attr("width", size.width)
      .attr("height", size.height)
      .attr("pointer-events", "all")
      .attr("fill", "rgba(255,255,255,0)")
      .on("mousemove", plotMousemove)
      .on("mousedown", plotDrag)
      .on("touchstart", plotDrag);

    plot.call(zoomBehavior());

    background = elem.append("div")
      .attr("class", "background")
      .style({
        "width": size.width + "px",
        "height": size.height + "px",
        "top": padding.top + "px",
        "left": padding.left + "px",
        "z-index": 0
      });

    createGraphCanvas();

    viewbox = vis.append("svg")
      .attr("class", "viewbox")
      .attr("top", 0)
      .attr("left", 0)
      .attr("width", size.width)
      .attr("height", size.height)
      .attr("viewBox", "0 0 "+size.width+" "+size.height);

    selectedRulerX = viewbox.append("line")
      .attr("stroke", gridStroke)
      .attr("stroke-dasharray", "2,2")
      .attr("y1", 0)
      .attr("y2", size.height)
      .attr("x1", function() { return selected === null ? 0 : selected[0]; } )
      .attr("x2", function() { return selected === null ? 0 : selected[0]; } )
      .attr("class", "ruler hidden");

    selectedRulerY = viewbox.append("line")
      .attr("stroke", gridStroke)
      .attr("stroke-dasharray", "2,2")
      .attr("x1", 0)
      .attr("x2", size.width)
      .attr("y1", function() { return selected === null ? 0 : selected[1]; } )
      .attr("y2", function() { return selected === null ? 0 : selected[1]; } )
      .attr("class", "ruler hidden");

    yAxisDraggable = svg.append("rect")
      .attr("class", "axis axis-y" + (options.enableAxisScaling ? " axis-draggable" : ""))
      .attr("x", padding.left-yAxisDraggableWidth)
      .attr("y", padding.top)
      .attr("rx", yAxisNumberHeight/6)
      .attr("width", yAxisDraggableWidth)
      .attr("height", size.height)
      .attr("pointer-events", "all")
      .on("mousedown", yAxisDrag)
      .on("touchstart", yAxisDrag);

    yAxisDraggableTooltip = yAxisDraggable.append("title");

    xAxisDraggable = svg.append("rect")
      .attr("class", "axis axis-x" + (options.enableAxisScaling ? " axis-draggable" : ""))
      .attr("x", padding.left)
      .attr("y", size.height+padding.top)
      .attr("rx", yAxisNumberHeight/6)
      .attr("width", size.width)
      .attr("height", xAxisDraggableHeight)
      .attr("pointer-events", "all")
      .on("mousedown", xAxisDrag)
      .on("touchstart", xAxisDrag);

    xAxisDraggableTooltip = xAxisDraggable.append("title");

    if (sizeType.value <= 2 && options.ylabel) {
      xAxisDraggableTooltip.text(options.xlabel);
    }

    if (sizeType.catefory && options.ylabel) {
      yAxisDraggableTooltip.text(options.ylabel);
    }

    adjustAxisDraggableFill();

    brush_element = viewbox.append("g")
          .attr("class", "brush");

    // Add the x-axis label
    if (sizeType.value > 2) {
      xlabel = vis.append("text")
          .attr("class", "axis")
          .attr("class", "xlabel")
          .text(options.xlabel)
          .attr("x", size.width/2)
          .attr("y", size.height)
          .attr("dy", xAxisLabelBaseline + "px")
          .style("text-anchor","middle");
    }

    // add y-axis label
    if (sizeType.value > 2) {
      ylabel = vis.append("g").append("text")
          .attr("class", "axis")
          .attr("class", "ylabel")
          .text( options.ylabel)
          .style("text-anchor","middle")
          .attr("transform","translate(" + yAxisLabelBaseline + " " + size.height/2+") rotate(-90)");
      if (sizeType.category === "small") {
        yAxisDraggable.append("title")
          .text(options.ylabel);
      }
    }

    // add Chart Title
    if (options.title && sizeType.value > 0) {
      titleBackground = svg.append("rect")
        .attr("class", "title-background")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", size.width + padding.left)
        // Leave some space for the last tick mark number.
        .attr("height", padding.top - halfFontSizeInPixels * 0.8);

      title = svg.selectAll("text")
        .data(titles, function(d) { return d; });
      title.enter().append("text")
        .attr("class", "title")
        .text(function(d) { return d; })
        .attr("x", options.titlePosition === "center" ?
          function() { return padding.left + size.width/2 - Math.min(size.width, getComputedTextLength(this))/2; } : titleLeftPadding)
        .attr("y", titleFontSizeInPixels * 1.1)
        .attr("dy", function(d, i) { return -i * titleFontSizeInPixels + "px"; });
      titleTooltip = title.append("title")
        .text("");
    } else if (options.title) {
      titleTooltip = plot.append("title")
        .text(options.title);
    }

    d3.select(node)
        .on("mousemove.drag", mousemove)
        .on("touchmove.drag", mousemove)
        .on("mouseup.drag",   mouseup)
        .on("touchend.drag",  mouseup);

    notification = vis.append("text")
        .attr("class", "graph-notification")
        .text(message)
        .attr("x", size.width/2)
        .attr("y", size.height/2)
        .style("text-anchor","middle");

    updateMarkers();
    updateRulers();
  }

  //
  // Repaint an existing graph by rescaling/updating the SVG and Canvas elements
  //
  function repaintExistingGraph() {
    vis
      .attr("width",  cx)
      .attr("height", cy)
      .attr("transform", "translate(" + padding.left + "," + padding.top + ")");

    plot
      .attr("width", size.width)
      .attr("height", size.height);

    background
      .style({
        "width":   size.width + "px",
        "height":  size.height + "px",
        "top":     padding.top + "px",
        "left":    padding.left + "px",
        "z-index": 0
      });

    viewbox
        .attr("top", 0)
        .attr("left", 0)
        .attr("width", size.width)
        .attr("height", size.height)
        .attr("viewBox", "0 0 "+size.width+" "+size.height);

    yAxisDraggable
        .attr("x", padding.left-yAxisDraggableWidth)
        .attr("y", padding.top-yAxisNumberHeight/2)
        .attr("width", yAxisDraggableWidth)
        .attr("height", size.height+yAxisNumberHeight);

    xAxisDraggable
        .attr("x", padding.left)
        .attr("y", size.height+padding.top)
        .attr("width", size.width)
        .attr("height", xAxisDraggableHeight);

    adjustAxisDraggableFill();

    if (options.title && sizeType.value > 0) {
      titleBackground
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", size.width + padding.left)
        // Leave some space for the last tick mark number.
        .attr("height", titleFontSizeInPixels * 1.8 - halfFontSizeInPixels * 0.8);

      title
          .attr("x", options.titlePosition === "center" ?
                     function() { return padding.left + size.width/2 - Math.min(size.width, getComputedTextLength(this))/2; } : titleLeftPadding)
          .attr("y", titleFontSizeInPixels * 1.1)
          .attr("dy", function(d, i) { return -i * titleFontSizeInPixels + "px"; });
      titleTooltip
          .text("");
    } else if (options.title) {
      titleTooltip
          .text(options.title);
    }

    if (options.xlabel && sizeType.value > 2) {
      xlabel
          .attr("x", size.width/2)
          .attr("y", size.height)
          .attr("dy", xAxisLabelBaseline + "px");
      xAxisDraggableTooltip
          .text("");
    } else {
      xAxisDraggableTooltip
          .text(options.xlabel);
    }

    if (options.ylabel && sizeType.value > 2) {
      var baseline = yAxisLabelBaseline;
      ylabel
        .attr("transform","translate(" + baseline + " " + size.height/2+") rotate(-90)");
      yAxisDraggableTooltip
        .text("");
    } else {
      yAxisDraggableTooltip
        .text(options.ylabel);
    }

    notification
      .attr("x", size.width/2)
      .attr("y", size.height/2);

    vis.selectAll("g.x").remove();
    vis.selectAll("g.y").remove();

    if (has_selection && selection_visible) {
      updateBrushElement();
    }

    updateMarkers();
    updateRulers();
    resizeCanvas();
    resizeButtonLayer();
  }

  function getComputedTextLength(el) {
    if (el.getComputedTextLength) {
      return el.getComputedTextLength();
    } else {
      return 100;
    }
  }

  function adjustAxisDraggableFill() {
    if (sizeType.value <= 1) {
      xAxisDraggable
        .style({
          "fill":       "rgba(196, 196, 196, 0.2)"
        });
      yAxisDraggable
        .style({
          "fill":       "rgba(196, 196, 196, 0.2)"
        });
    } else {
      xAxisDraggable
        .style({
          "fill":       null
        });
      yAxisDraggable
        .style({
          "fill":       null
        });
    }
  }

  function zoomBehavior() {
    if (options.enableZooming) {
      return d3.behavior.zoom().x(xScale).y(yScale).on("zoom", redraw)
    } else {
      // noop
      return function () {};
    }
  }

  //
  // Redraw the plot and axes when plot is translated or axes are re-scaled
  //
  function redraw() {
    updateAxesAndSize();
    repaintExistingGraph();
    // Regenerate x-ticks
    var gx = vis.selectAll("g.x")
        .data(xScale.ticks(xTickCount), String)
        .attr("transform", tx);

    var gxe = gx.enter().insert("g", "a")
        .attr("class", "x")
        .attr("transform", tx);

    gxe.append("line")
        .attr("stroke", gridStroke)
        .attr("y1", 0)
        .attr("y2", size.height);

    if (sizeType.value > 1 && !options.hideAxisValues) {
      gxe.append("text")
          .attr("class", "axis")
          .attr("y", size.height)
          .attr("dy", axisFontSizeInPixels + "px")
          .attr("text-anchor", "middle")
          .text(fx)
          .on("mouseover", function() { d3.select(this).style("font-weight", "bold");})
          .on("mouseout",  function() { d3.select(this).style("font-weight", "normal");});
    }

    gx.exit().remove();

    // Regenerate y-ticks
    var gy = vis.selectAll("g.y")
        .data(yScale.ticks(yTickCount), String)
        .attr("transform", ty);

    var gye = gy.enter().insert("g", "a")
        .attr("class", "y")
        .attr("transform", ty)
        .attr("background-fill", "#FFEEB6");

    gye.append("line")
        .attr("stroke", gridStroke)
        .attr("x1", 0)
        .attr("x2", size.width);

    if (sizeType.value > 1) {
      if (options.yscale === "log") {
        var gye_length = gye[0].length;
        if (gye_length > 100) {
          gye = gye.filter(function(d) { return !!d.toString().match(/(\.[0]*|^)[1]/);});
        } else if (gye_length > 50) {
          gye = gye.filter(function(d) { return !!d.toString().match(/(\.[0]*|^)[12]/);});
        } else {
          gye = gye.filter(function(d) {
            return !!d.toString().match(/(\.[0]*|^)[125]/);});
        }
      }

      if(!options.hideAxisValues){
        gye.append("text")
          .attr("class", "axis")
          .attr("x", -axisFontSizeInPixels/4 + "px")
          .attr("dy", ".35em")
          .attr("text-anchor", "end")
          .style("cursor", "ns-resize")
          .text(fy)
          .on("mouseover", function() { d3.select(this).style("font-weight", "bold");})
          .on("mouseout",  function() { d3.select(this).style("font-weight", "normal");});
      }
    }

    gy.exit().remove();

    // For now, only annotations are of annotation.type === 'line' are supported
    // so only generate attribute hash for lines and assume that we can directly
    // append svg nodes of annotation.type

    function annotationAttributes(d) {
      switch(d.type) {
      case "line":
        return {
          stroke: d.data.hasOwnProperty("stroke") ? d.data.stroke : "#f00",
          x1: d.data.hasOwnProperty('x1') ? xScale(d.data.x1) : 0,
          x2: d.data.hasOwnProperty('x2') ? xScale(d.data.x2) : size.width,
          y1: d.data.hasOwnProperty('y1') ? yScale(d.data.y1) : 0,
          y2: d.data.hasOwnProperty('y2') ? yScale(d.data.y2) : size.height
        };
      case "bar":
        return {
          stroke: d.data.hasOwnProperty("stroke") ? d.data.stroke : "#f00",
          fill:   d.data.hasOwnProperty("stroke") ? d.data.stroke : "#f00",
          x:      d.data.hasOwnProperty('x1') ? xScale(d.data.x1) : 0,
          y:      d.data.hasOwnProperty('y2') ? yScale(d.data.y2) : 0,
          width:  d.data.hasOwnProperty('x2') ? xScale(d.data.x2)-xScale(d.data.x1) : size.width,
          height: d.data.hasOwnProperty('y1') ? yScale(d.data.y1)-yScale(d.data.y2) : size.height,
          "fill-opacity": 0.5
        };
      }

      return {};
    }

    var annotationTypes = {
      line: "line",
      bar:  "rect"
    };

    var annotationsSelection = vis.selectAll("g.annotation")
      .data(annotations);

    // create annotation objects if necessary
    annotationsSelection.enter()
      .append("g")
      .attr("class", "annotation")
      .each(function(d,i){
        d3.select(this).append(annotationTypes[d.type]);
      });

    // update annotation attributes to reflect current graph state
    annotationsSelection.each(function(d,i){
      d3.select(this.childNodes[0]).attr(annotationAttributes(d))
        .call(zoomBehavior());
    });

    annotationsSelection.exit().remove();
    plot.call(zoomBehavior());
    update();
  }

  // ------------------------------------------------------------
  //
  // Rendering: Updating samples/data points in the plot
  //
  // ------------------------------------------------------------


  //
  // Update plotted data, optionally pass in new samplePoint
  //
  function update(samplePoint) {
    setCurrentSample(samplePoint);
    updateCanvasFromPoints(currentSample);
    updateMarkers();
    if (d3.event && d3.event.keyCode) {
      d3.event.preventDefault();
      d3.event.stopPropagation();
    }
  }

  // samplePoint is optional argument
  function updateOrRescale(samplePoint) {
    setCurrentSample(samplePoint);

    if (autoscale()) {
      redraw();
    } else {
      update(currentSample);
    }
  }

  function circleClasses(d) {
    var cs = [];
    if (d === selected) {
      cs.push("selected");
    }
    if (cs.length === 0) {
      return null;
    } else {
      return cs.join(" ");
    }
  }

  function updateMarkerRadius() {
    var d = xScale.domain(),
        r = xScale.range();
    markerRadius = (r[1] - r[0]) / ((d[1] - d[0]));
    markerRadius = Math.min(Math.max(markerRadius, 4), 8);
    markerStrokeWidth = markerRadius/3;
  }

  function updateMarkers() {
    var marker,
        markedPoints = null;
    if (options.markAllDataPoints && sizeType.value > 1) {
      markedPoints = [];
      markedPoints = markedPoints.concat.apply(markedPoints, pointArray);
    } else if (options.markNearbyDataPoints && sizeType.value > 1) {
      markedPoints = selectable.slice(0);
      if (selected !== null && markedPoints.indexOf(selected) === -1) {
        markedPoints.push(selected);
      }
    }
    if (markedPoints !== null) {
      updateMarkerRadius();
      marker = vis.select("svg").selectAll("circle").data(markedPoints);
      marker.enter().append("circle")
          .attr("class", circleClasses)
          .attr("cx",    function(d) { return xScale(d[0]); })
          .attr("cy",    function(d) { return yScale(d[1]); })
          .attr("r", markerRadius)
          .style("stroke-width", markerStrokeWidth)
          .style("cursor", markerCursorStyle)
          .on("mousedown.drag",  dataPointDrag)
          .on("touchstart.drag", dataPointDrag)
          .append("title")
          .text(function(d) { return "( " + fx(d[0]) + ", " + fy(d[1]) + " )"; });

      marker
          .attr("class", circleClasses)
          .attr("cx",    function(d) { return xScale(d[0]); })
          .attr("cy",    function(d) { return yScale(d[1]); })
          .select("title")
          .text(function(d) { return "( " + fx(d[0]) + ", " + fy(d[1]) + " )"; });

      marker.exit().remove();
    }

    updateRulers();
  }

  function updateRulers() {
    if (options.showRulersOnSelection && selected !== null) {
      selectedRulerX
        .attr("y1", 0)
        .attr("y2", size.height)
        .attr("x1", function() { return selected === null ? 0 : xScale(selected[0]); } )
        .attr("x2", function() { return selected === null ? 0 : xScale(selected[0]); } )
        .attr("class", function() { return "ruler" + (selected === null ? " hidden" : ""); } );

      selectedRulerY
        .attr("x1", 0)
        .attr("x2", size.width)
        .attr("y1", function() { return selected === null ? 0 : yScale(selected[1]); } )
        .attr("y2", function() { return selected === null ? 0 : yScale(selected[1]); } )
        .attr("class", function() { return "ruler" + (selected === null ? " hidden" : ""); } );
    } else {
      selectedRulerX.attr("class", "ruler hidden");
      selectedRulerY.attr("class", "ruler hidden");
    }
  }


  // ------------------------------------------------------------
  //
  // UI Interaction: Plot dragging and translation; Axis re-scaling
  //
  // ------------------------------------------------------------

  function plotMousemove() {
    if (options.markNearbyDataPoints) {
      var mousePoint = d3.mouse(vis.node()),
          translatedMousePointX = xScale.invert(Math.max(0, Math.min(size.width, mousePoint[0]))),
          p,
          idx, pMin, pMax,
          i;
      // highlight the central point, and also points to the left and right
      // TODO Handle multiple data sets/lines
      selectable = [];
      for (i = 0; i < pointArray.length; i++) {
        points = pointArray[i];
        p = findClosestPointByX(translatedMousePointX, i);
        if (p !== null) {
          idx = points.indexOf(p);
          pMin = idx - (options.extraCirclesVisibleOnHover);
          pMax = idx + (options.extraCirclesVisibleOnHover + 1);
          if (pMin < 0) { pMin = 0; }
          if (pMax > points.length - 1) { pMax = points.length; }
          selectable = selectable.concat(points.slice(pMin, pMax));
        }
      }
      update();
    }
  }

  function findClosestPointByX(x, line) {
    if (typeof(line) === "undefined" || line === null) { line = 0; }
    // binary search through points.
    // This assumes points is sorted ascending by x value, which for realTime graphs is true.
    points = pointArray[line];
    if (points.length === 0) { return null; }
    var min = 0,
        max = points.length - 1,
        mid, p1, p2, p3;
    while (min < max) {
      mid = Math.floor((min + max)/2.0);
      if (points[mid][0] < x) {
        min = mid + 1;
      } else {
        max = mid;
      }
    }

    // figure out which point is actually closest.
    // we have to compare 3 points, to account for floating point rounding errors.
    // if the mouse moves off the left edge of the graph, p1 may not exist.
    // if the mouse moves off the right edge of the graph, p3 may not exist.
    p1 = points[mid - 1];
    p2 = points[mid];
    p3 = points[mid + 1];
    if (typeof(p1) !== "undefined" && Math.abs(p1[0] - x) <= Math.abs(p2[0] - x)) {
      return p1;
    } else if (typeof(p3) === "undefined" || Math.abs(p2[0] - x) <= Math.abs(p3[0] - x)) {
      return p2;
    } else {
      return p3;
    }
  }

  function plotDrag() {
    var p;
    if (draw_enabled) {
      d3.event.preventDefault();
      p = d3.mouse(vis.node());
      addPointAtMouse(p);
      downx = p[0];
      downy = p[0];
      draggedPoint = false;
    } else if(options.enableAxisScaling) {
      d3.event.preventDefault();
      d3.select('body').style("cursor", "move");
      if (d3.event.altKey) {
        plot.style("cursor", "nesw-resize");
        if (d3.event.shiftKey && options.addData) {
          addPointAtMouse();
        } else {
          p = d3.mouse(vis.node());
          downx = xScale.invert(p[0]);
          downy = yScale.invert(p[1]);
          draggedPoint = false;
          d3.event.stopPropagation();
        }
        // d3.event.stopPropagation();
      }
    }
  }

  function notifyPointListeners(action, point) {
    pointListeners.forEach(function(callback) {
      callback.call(null,{action: action, point: point});
    });
  }

  function isPointInsideGraph(p) {
    var graphx = xScale.invert(p[0]),
        graphy = yScale.invert(p[1]),
        xAxisStart = xScale.domain()[0],
        xAxisEnd =   xScale.domain()[1],
        yAxisStart = yScale.domain()[0],
        yAxisEnd =   yScale.domain()[1];

    return graphx >= xAxisStart && graphx <= xAxisEnd && graphy >= yAxisStart && graphy <= yAxisEnd;
  }

  function addPointAtMouse(p) {
    if (!p) {
      p = d3.mouse(vis.node());
    }

    var newpoint = [],
        newpointIdx,
        pointsIndexed = pointArrayIndexed[options.drawIndex];
    points = pointArray[options.drawIndex];
    newpoint[0] = xScale.invert(Math.max(0, Math.min(size.width,  p[0])));
    newpoint[1] = yScale.invert(Math.max(0, Math.min(size.height, p[1])));
    points.push(newpoint);
    pointsIndexed.push(newpoint);
    notifyPointListeners("added", newpoint);
    processPointsArray(points);
    selected = newpoint;

    // update currentSample
    newpointIdx = points.indexOf(newpoint);
    if (currentSample == points.length-2 || currentSample >= newpointIdx) {
      // currentSample was pointing to the last point, so keep it at the last point
      currentSample++;
    }

    points = pointArray[0];
    update();
  }

  function isBetween(a,b,p) {
    return a < p && p <= b;
  }

  function isBetweenReversed(a,b,p) {
    return a <= p && p < b;
  }

  function clearPointsBetween(x1, x2) {
    var a = x1,
        b = x2,
        needsUpdate = false,
        between = isBetween,
        i, p, removed, pointsIndexed, newPoints;

    // Check to make sure a is always smaller than b
    if (x1 > x2) {
      a = x2;
      b = x1;
      between = isBetweenReversed;
    }

    pointsIndexed = pointArrayIndexed[options.drawIndex];

    // for (i = points.length-1; i >= 0; i--) {
    for (i = 0; i < pointsIndexed.length; i++) {
      p = pointsIndexed[i];
      if (p && between(a, b, p[0])) {
        // null the point
        removed = pointsIndexed[i].slice();
        pointsIndexed[i][0] = null;
        pointsIndexed[i][1] = null;
        notifyPointListeners("removed", removed);
        needsUpdate = true;
      }
    }
    if (needsUpdate) {
      newPoints = copyNonNull(pointsIndexed);
      processPointsArray(newPoints);
      pointArray[options.drawIndex] = newPoints;
      points = pointArray[0];
      update();
    }
  }

  function falseFunction() {
    return false;
  }

  function xAxisDrag() {
    if(options.enableAxisScaling) {
      node.focus();
      document.onselectstart = falseFunction;
      d3.event.preventDefault();
      var p = d3.mouse(vis.node());
      downx = xScale.invert(p[0]);
    }
  }

  function yAxisDrag() {
    if(options.enableAxisScaling) {
      node.focus();
      d3.event.preventDefault();
      document.onselectstart = falseFunction;
      var p = d3.mouse(vis.node());
      downy = yScale.invert(p[1]);
    }
  }

  function dataPointDrag(d) {
    node.focus();
    d3.event.preventDefault();
    document.onselectstart = falseFunction;
    if (selected === d) {
      selected = draggedPoint = null;
    } else {
      selected = draggedPoint = d;
    }
    update();
  }

  function mousemove() {
    var p = d3.mouse(vis.node()),
        points,
        index,
        px,
        x,
        nextPoint,
        prevPoint,
        minusHalf,
        plusHalf;

    // t = d3.event.changedTouches;

    document.onselectstart = function() { return true; };
    d3.event.preventDefault();
    if (draggedPoint) {
      if (options.dataChange) {
        draggedPoint[1] = yScale.invert(Math.max(0, Math.min(size.height, p[1])));
      } else {
        pointArray.forEach(function (arr) {
          var i = arr.indexOf(draggedPoint);
          if (i !== -1) {
            points = arr;
            index = i;
          }
        });

        if (index && index < (points.length-1)) {
          px = xScale.invert(p[0]);
          x = draggedPoint[0];
          nextPoint = points[index+1];
          prevPoint = points[index-1];
          minusHalf = x - (x - prevPoint[0])/2;
          plusHalf =  x + (nextPoint[0] - x)/2;
          if (px < minusHalf) {
            draggedPoint = prevPoint;
            selected = draggedPoint;
          } else if (px > plusHalf) {
            draggedPoint = nextPoint;
            selected = draggedPoint;
          }
        }
      }
      update();
    }

    if (draw_enabled && !isNaN(downx) && !isNaN(downy)) {
      if (isPointInsideGraph(p)) {
        clearPointsBetween(xScale.invert(Math.max(0, Math.min(size.width,  downx))), xScale.invert(Math.max(0, Math.min(size.width,  p[0]))));
        addPointAtMouse(p);
        downx = p[0];
        downy = p[0];
      } else {
        mouseup();
      }
      d3.event.stopPropagation();
    } else {
      if (!isNaN(downx)) {
        d3.select('body').style("cursor", "col-resize");
        plot.style("cursor", "col-resize");
        xScale.domain(axis.axisProcessDrag(downx, xScale.invert(p[0]), xScale.domain()));
        updateMarkerRadius();
        redraw();
        d3.event.stopPropagation();
      }

      if (!isNaN(downy)) {
        d3.select('body').style("cursor", "row-resize");
        plot.style("cursor", "row-resize");
        yScale.domain(axis.axisProcessDrag(downy, yScale.invert(p[1]), yScale.domain()));
        redraw();
        d3.event.stopPropagation();
      }
    }
  }

  function mouseup() {
    d3.select('body').style("cursor", "auto");
    plot.style("cursor", "auto");
    document.onselectstart = function() { return true; };
    if (!isNaN(downx)) {
      redraw();
      downx = NaN;
    }
    if (!isNaN(downy)) {
      redraw();
      downy = NaN;
    }
    draggedPoint = null;
  }

  //------------------------------------------------------
  //
  // Autoscale
  //
  // ------------------------------------------------------------

  /**
    If there are more than 1 data points, scale axes. Default behavior is to expand domain only when
    corresponding "autoScaleX" and "autoScaleY" options are set to true.

    However if you pass <true> as an argument, it will enforce scaling of axes so the fit data.
  */
  function autoscale(fit) {
    var maxPointsLen = -Infinity;
    var domainXChanged;
    var domainYChanged;
    var ret;

    pointArray.forEach(function (arr) {
      if (arr.length > maxPointsLen) maxPointsLen = arr.length;
    });

    if (maxPointsLen > 1) {
      if (options.autoScaleX || fit) {
        var xPadding = fit ? 0.05 : options.autoScalePadding;
        domainXChanged = scaleAxis("x", pointsXMin, pointsXMax, xPadding, fit);
      }
      if (options.autoScaleY || fit) {
        var yPadding = fit ? 0.05 : options.autoScalePadding;
        domainYChanged = scaleAxis("y", pointsYMin, pointsYMax, yPadding, fit);
      }
      ret = domainXChanged || domainYChanged;
    } else {
      ret = undefined;
    }

    // Only call callback if there's what we think of as an "autoscale was clicked" event, which
    // specifically means the case that fit == true
    if (fit && options.onAutoscale) {
      options.onAutoscale.call(null);
    }

    return ret;
  }

  function scaleAxis(axis, minVal, maxVal, padding, fit) {
    if (minVal === maxVal) {
      // Simply skip scaling when min === max.
      return false;
    }
    // axis argument is expected to be "x" or "y".
    var scale = axis === "x" ? xScale : yScale;
    var dMin = scale.domain()[0];
    var dMax = scale.domain()[1];
    var domainChanged = false;
    // Like Math.pow but returns a value with the same sign as x: pow(-1, 0.5) -> -1
    var pow = function(x, exponent) {
      return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent);
    };
    // Convert min, max to a linear scale, and set 'transform' to the function that
    // converts the new min, max to the relevant scale.
    var transform;
    switch (options[axis + "scale"]) {
      case 'linear':
        transform = function(x) { return x; };
        break;
      case 'log':
        minVal = Math.log(minVal) / Math.log(10);
        maxVal = Math.log(maxVal) / Math.log(10);
        transform = function(x) { return Math.pow(10, x); };
        break;
      case 'pow':
        var scaleExponent = options[axis + "scaleExponent"];
        minVal = pow(minVal, scaleExponent);
        maxVal = pow(maxVal, scaleExponent);
        transform = function(x) { return pow(x, 1 / scaleExponent); };
        break;
    }

    var pad = (maxVal - minVal) * padding;
    if (maxVal > dMax || fit) {
      dMax = maxVal + pad;
      domainChanged = true;
    }
    if (minVal < dMin || fit) {
      dMin = minVal - pad;
      domainChanged = true;
    }
    if (domainChanged) {
      scale.domain([transform(dMin), transform(dMax)]).nice();
    }
    return domainChanged;
  }

  // ------------------------------------------------------------
  //
  // Brush Selection
  //
  // ------------------------------------------------------------

  function toggleSelection() {
    drawEnabled(false);
    if (!selectionVisible()) {
      // The graph model defaults to visible=false and enabled=true.
      // Reset these so that this first click turns on selection correctly.
      selectionEnabled(false);
      selectionVisible(true);
    }
    if (!!selectionEnabled()) {
      if (options.clearSelectionOnLeavingSelectMode || selectionDomain() === []) {
        selectionDomain(null);
      }
      selectionEnabled(false);
    } else {
      if (selectionDomain() == null) {
        selectionDomain([]);
      }
      selectionEnabled(true);
    }
  }

  /**
    Set or get the selection domain (i.e., the range of x values that are selected).

    Valid domain specifiers:
      null     no current selection (selection is turned off)
      []       a current selection exists but is empty (has_selection is true)
      [x1, x2] the region between x1 and x2 is selected. Any data points between
               x1 and x2 (inclusive) would be considered to be selected.

    Default value is null.
  */
  function selectionDomain(a) {

    if (!arguments.length) {
      if (!has_selection) {
        return null;
      }
      if (selection_region.xmax === Infinity && selection_region.xmin === Infinity ) {
        return [];
      }
      return [selection_region.xmin, selection_region.xmax];
    }

    // setter

    if (a === null) {
      has_selection = false;
    }
    else if (a.length === 0) {
      has_selection = true;
      selection_region.xmin = Infinity;
      selection_region.xmax = Infinity;
    }
    else {
      has_selection = true;
      selection_region.xmin = a[0];
      selection_region.xmax = a[1];
    }

    updateBrushElement();

    if (selection_listener) {
      selection_listener(selectionDomain());
    }
    return api;
  }

  /**
    Get whether the graph currently has a selection region. Default value is false.

    If true, it would be valid to filter the data points to return a subset within the selection
    region, although this region may be empty!

    If false the graph is not considered to have a selection region.

    Note that even if has_selection is true, the selection region may not be currently shown,
    and if shown, it may be empty.
  */
  function hasSelection() {
    return has_selection;
  }

  /**
    Set or get the visibility of the selection region. Default value is false.

    Has no effect if the graph does not currently have a selection region
    (selection_domain is null).

    If the selection_enabled property is true, the user will also be able to interact
    with the selection region.
  */
  function selectionVisible(val) {
    if (!arguments.length) {
      return selection_visible;
    }

    // setter
    val = !!val;
    if (selection_visible !== val) {
      selection_visible = val;
      updateBrushElement();
    }
    return api;
  }

  /**
    Set or get whether user manipulation of the selection region should be enabled
    when a selection region exists and is visible. Default value is true.

    Setting the value to true has no effect unless the graph has a selection region
    (selection_domain is non-null) and the region is visible (selection_visible is true).
    However, the selection_enabled setting is honored whenever those properties are
    subsequently updated.

    Setting the value to false does not affect the visibility of the selection region,
    and does not affect the ability to change the region by calling selectionDomain().

    Note that graph panning and zooming are disabled while selection manipulation is enabled.
  */
  function selectionEnabled(val) {
    if (!arguments.length) {
      return selection_enabled;
    }

    // setter
    val = !!val;
    if (selection_enabled !== val) {
      selection_enabled = val;

      if (selectionButton) {
        if (val) {
          selectionButton.classed("active", true);
        } else {
          selectionButton.classed("active", false);
        }
      }

      updateBrushElement();
    }
    return api;
  }

  /**
    Set or get the listener to be called when the selection_domain changes.

    Both programatic and interactive updates of the selection region result in
    notification of the listener.

    The listener is called with the new selection_domain value in the first argument.
  */
  function selectionListener(cb) {
    if (!arguments.length) {
      return selection_listener;
    }
    // setter
    selection_listener = cb;
    return api;
  }

  function brushListener() {
    var extent;
    if (selection_enabled) {
      // Note there is a brush.empty() method, but it still reports true after the
      // brush extent has been programatically updated.
      extent = brush_control.extent();
      selectionDomain( extent[0] !== extent[1] ? extent : [] );
    }
  }

  function updateBrushElement() {
    if (has_selection && selection_visible) {
      brush_control = brush_control || d3.svg.brush()
        .x(xScale)
        .extent([selection_region.xmin || 0, selection_region.xmax || 0])
        .on("brush", brushListener);

      brush_element
        .call(brush_control.extent([selection_region.xmin || 0, selection_region.xmax || 0]))
        .style('display', 'inline')
        .style('pointer-events', selection_enabled ? 'all' : 'none')
        .selectAll("rect")
          .attr("height", size.height);

    } else {
      brush_element.style('display', 'none');
    }
  }

  // ------------------------------------------------------------
  //
  // Drawing
  //
  // ------------------------------------------------------------

  function toggleDraw() {
    if (has_selection && selection_visible) {
      toggleSelection();
    }
    drawEnabled(!draw_enabled);
  }

  function drawEnabled(val) {
    if (!arguments.length) {
      return draw_enabled;
    }

    // setter
    val = !!val;
    if (draw_enabled !== val) {
      draw_enabled = val;

      if (drawButton) {
        if (val) {
          drawButton.classed("active", true);
        } else {
          drawButton.classed("active", false);
        }
      }
    }
    return api;
  }

  // ------------------------------------------------------------
  //
  // Legend
  //
  // ------------------------------------------------------------

  function toggleLegend() {
    options.legendVisible = !options.legendVisible;
    updateLegendVisibility();
  }

  function updateLegendVisibility() {
    if (legendButton) {
      if (!!options.legendVisible) {
        legendButton.classed("active", true);
      } else {
        legendButton.classed("active", false);
      }
    }
    if (legendLayer) {
      if (!!options.legendVisible) {
        legendLayer.classed("legend-invisible", false);
        // Reposition while we're at it
        legendLayer
          .style({
            "top":     padding.top + halfFontSizeInPixels + "px",
            "right":   padding.right + halfFontSizeInPixels +
                       (options.showButtons && options.buttonsLayout === "vertical" ? buttonLayer.property('clientWidth') : 0) + "px"
          });
      } else {
        legendLayer.classed("legend-invisible", true);
      }
    }
    return api;
  }

  // ------------------------------------------------------------
  //
  // Canvas-based plotting
  //
  // ------------------------------------------------------------

  function createGraphCanvas() {
    graphCanvas = elem.append("canvas");
    gcanvas = graphCanvas.node();
    resizeCanvas();
  }

  function resizeCanvas() {
    graphCanvas
      .attr("class", "overlay")
      .style({
        "position": "absolute",
        "width":    size.width + "px",
        "height":   size.height + "px",
        "top":      padding.top + "px",
        "left":     padding.left + "px",
        "z-index": 1
      });
    gcanvas = graphCanvas.node();
    gcanvas.width = size.width;
    gcanvas.height = size.height;
    gcanvas.top = padding.top;
    gcanvas.left = padding.left;
    setupCanvasContext();
    updateCanvasFromPoints(currentSample);
  }

  function clearCanvas() {
    if (gcanvas.getContext) {
      gcanvas.width = gcanvas.width;
      gctx.lineWidth = lineWidth;
      gctx.fillStyle = canvasFillStyle;
      gctx.fillRect(0, 0, gcanvas.width, gcanvas.height);
      gctx.strokeStyle = "rgba(255,65,0, 1.0)";
      gctx.globalAlpha = 1;
    }
  }

  function setupCanvasContext() {
    if (gcanvas.getContext) {
      gctx = gcanvas.getContext( '2d' );
      gctx.globalCompositeOperation = "source-over";
      gctx.lineWidth = lineWidth;
      gctx.fillStyle = canvasFillStyle;
      gctx.fillRect(0, 0, gcanvas.width, gcanvas.height);
      gctx.strokeStyle = "rgba(255,65,0, 1.0)";
      gctx.globalAlpha = 1;
    }
  }

  //
  // Update Canvas plotted data from [x, y] data points
  //
  function updateCanvasFromPoints(samplePoint) {
    var i, j, len,
        dx,
        px, py,
        index,
        yOrigin = yScale(0.00001),
        lines = options.lines,
        bars = options.bars,
        pointsLength,
        numberOfLines = pointArray.length,
        xAxisStart,
        xAxisEnd,
        pointStop,
        start;

    // hack for lack of canvas support in jsdom tests
    if (typeof gcanvas.getContext === "undefined" ) { return; }

    setCurrentSample(samplePoint);
    clearCanvas();
    gctx.fillRect(0, 0, gcanvas.width, gcanvas.height);
    gctx.lineWidth = lineWidth;
    xAxisStart = xScale.domain()[0];
    xAxisEnd =   xScale.domain()[1];
    start = Math.max(0, xAxisStart);
    if (lines) {
      for (i = 0; i < numberOfLines; i++) {
        points = pointArray[i];
        pointsLength = points.length;
        if (pointsLength === 0) {
          continue;
        } else if (pointsLength === 1) {
          // Draw just single point.
          setFillColor(i);
          gctx.fillRect(xScale(points[0][0]), yScale(points[0][1]), lineWidth, lineWidth);
          continue;
        }
        index = 0;
        // find first point >= xAxisStart
        for (j = 0; j < pointsLength; j++) {
          if (points[j][0] != null && points[j][1] != null && points[j][0] >= xAxisStart) { break; }
          index++;
        }
        if (index >= pointsLength) { continue; }
        if (index > 0) { index--; }
        px = xScale(points[index][0]);
        py = yScale(points[index][1]);
        setStrokeColor(i);
        gctx.beginPath();
        gctx.moveTo(px, py);
        dx = points[index][0];
        index++;
        // plot all ... or until one point past xAxisEnd
        // or until we reach currentSample
        for (len = Math.min(samplePoint, pointsLength); index < len; index++) {
          if (points[index][0] == null || points[index][1] == null) { continue; }
          dx = points[index][0];
          px = xScale(dx);
          py = yScale(points[index][1]);
          gctx.lineTo(px, py);
          if (dx >= xAxisEnd) { break; }
        }
        gctx.stroke();
        // now plot in a desaturated style all the rest of the points
        // ... or until one point past xAxisEnd
        if (index < pointsLength && dx < xAxisEnd) {
          setStrokeColor(i, true);
          gctx.lineWidth = lineWidth/2;
          for (;index < pointsLength; index++) {
            if (points[index][0] == null || points[index][1] == null) { continue; }
            dx = points[index][0];
            px = xScale(dx);
            py = yScale(points[index][1]);
            gctx.lineTo(px, py);
            if (dx >= xAxisEnd) { break; }
          }
          gctx.stroke();
          gctx.lineWidth = lineWidth;
        }
      }
    } else if (bars) {
      for (i = 0; i < numberOfLines; i++) {
        points = pointArray[i];
        pointsLength = points.length;
        setStrokeColor(i);
        pointStop = samplePoint - 1;
        for (index=start; index < pointStop; index++) {
          if (points[index][0] == null || points[index][1] == null) { continue; }
          px = xScale(points[index][0]);
          py = yScale(points[index][1]);
          if (py === 0) {
            continue;
          }
          gctx.beginPath();
          gctx.moveTo(px, yOrigin);
          gctx.lineTo(px, py);
          gctx.stroke();
        }
        pointStop = points.length-1;
        if (index < pointStop) {
          setStrokeColor(i, true);
          for (;index < pointStop; index++) {
            if (points[index][0] == null || points[index][1] == null) { continue; }
            px = xScale(points[index][0]);
            py = yScale(points[index][1]);
            gctx.beginPath();
            gctx.moveTo(px, yOrigin);
            gctx.lineTo(px, py);
            gctx.stroke();
          }
        }
      }
    } else {
      for (i = 0; i < numberOfLines; i++) {
        points = pointArray[i];
        pointsLength = points.length;
        index = 0;
        // find first point >= xAxisStart
        for (j = 0; j < pointsLength; j++) {
          if (points[j][0] != null && points[j][1] != null && points[j][0] >= xAxisStart) { break; }
          index++;
        }
        if (index > 0) { --index; }
        if (index >= pointsLength) { continue; }
        setFillColor(i);
        // plot all ... or until one point past xAxisEnd
        // or until we reach currentSample
        for (len = Math.min(samplePoint, pointsLength); index < len; index++) {
          if (points[index][0] == null || points[index][1] == null) { continue; }
          dx = points[index][0];
          px = xScale(dx);
          py = yScale(points[index][1]);
          gctx.fillRect(px, py, lineWidth, lineWidth);
          if (dx >= xAxisEnd) { break; }
        }
        // now plot in a desaturated style all the rest of the points
        // ... or until one point past xAxisEnd
        if (index < pointsLength && dx < xAxisEnd) {
          setFillColor(i, true);
          for (;index < pointsLength; index++) {
            if (points[index][0] == null || points[index][1] == null) { continue; }
            dx = points[index][0];
            px = xScale(dx);
            py = yScale(points[index][1]);
            gctx.fillRect(px, py, lineWidth, lineWidth);
            if (dx >= xAxisEnd) { break; }
          }
        }
      }
    }
  }

  function setStrokeColor(i, afterSamplePoint) {
    gctx.strokeStyle = getDataColor(i);
    gctx.globalAlpha = afterSamplePoint ? 0.5 : 1.0;
  }

  function setFillColor(i, afterSamplePoint) {
    gctx.fillStyle   = getDataColor(i);
    gctx.globalAlpha = afterSamplePoint ? 0.4 : 1.0;
  }

  function getDataColor(i) {
    var colorIndex = Math.min(i, options.dataColors.length - 1);
    return colorIndex < 0 ? "black" : options.dataColors[colorIndex];
  }

  // ------------------------------------------------------------
  //
  // Adding samples/data points
  //
  // ------------------------------------------------------------

  // Add an array of points then update the graph.
  function addPoints(datapoints) {
    addDataPoints(datapoints);
    setCurrentSample("last");
    updateOrRescale();
  }

  function replacePoints(datapoints, index) {
    setDataPoints(datapoints, index);
    setCurrentSample("last");
    updateOrRescale();
  }

  // Add an array of samples then update the graph.
  function addSamples(datasamples) {
    addDataSamples(datasamples);
    setCurrentSample("last");
    updateOrRescale();
  }


  // Add a point [x, y] by processing sample (Y value) synthesizing
  // X value from sampleInterval and number of points
  function addSample(sample) {
    var index = points.length,
        xvalue = (index * sampleInterval) + dataSampleStart,
        point = [ xvalue, sample ];
    points.push(point);
    setCurrentSample("last");
    updateOrRescale();
  }

  // Add a point [x, y] to points array
  function addPoint(pnt) {
    points.push(pnt);
    setCurrentSample("last");
    updateOrRescale();
  }

  function comparePoints(a, b) {
    if (a[0] < b[0])
       return -1;
    if (a[0] > b[0])
       return 1;
    return 0;
  }

  function checkPointsOrder(points, newPointIdx) {
    if (!options.sortPoints || points.length < 2) return;
    if (newPointIdx == null) {
      points.sort(comparePoints);
      return;
    }
    // This function assumes that 'points' array was sorted and one new point was added.
    // Sort points only when it's really necessary.
    var newPoint = points[newPointIdx];
    var prevPoint = points[newPointIdx - 1];
    var nextPoint = points[newPointIdx + 1];
    if ((prevPoint && prevPoint[0] > newPoint[0]) ||
        (nextPoint && newPoint[0] > nextPoint[0])) {
      points.sort(comparePoints);
    }
  }

  function updatePointsExtent(newPoint) {
    if (newPoint[0] < pointsXMin) pointsXMin = newPoint[0];
    if (newPoint[1] < pointsYMin) pointsYMin = newPoint[1];
    if (newPoint[0] > pointsXMax) pointsXMax = newPoint[0];
    if (newPoint[1] > pointsYMax) pointsYMax = newPoint[1];
  }

  // Add an array (or arrays) of points.
  function addDataPoints(datapoints) {
    var point;
    var points;
    var pointsIndexed;
    for (var i = 0, len = datapoints.length; i < len; i++) {
      if (datapoints[i] == null) continue;
      points = pointArray[i];
      pointsIndexed = pointArrayIndexed[i];
      if (points == null || pointsIndexed == null) {
        // Create a new data series dynamically in case of need.
        points = pointArray[i] = [];
        pointsIndexed = pointArrayIndexed[i] = [];
      }
      point = datapoints[i];
      points.push(point);
      pointsIndexed.push(point);
      updatePointsExtent(point);
      checkPointsOrder(points, points.length - 1);
    }
  }

  function setDataPoints(datapoints, index) {
    var oldPoint;
    var newPoint;
    var points;
    var pointsIndexed;
    var pointModified = false;
    for (var i = 0, len = datapoints.length; i < len; i++) {
      if (datapoints[i] == null) continue;
      points = pointArray[i];
      pointsIndexed = pointArrayIndexed[i];
      if (points == null || pointsIndexed == null) {
        // Create a new data series dynamically in case of need.
        points = pointArray[i] = [];
        pointsIndexed = pointArrayIndexed[i] = [];
      }
      oldPoint = pointsIndexed[index];
      newPoint = datapoints[i];
      if (oldPoint == null) {
        // Create new point.
        points.push(newPoint);
        pointsIndexed[index] = newPoint;
        checkPointsOrder(points, points.length - 1);
        updatePointsExtent(newPoint);
      } else {
        // Update coordinates manually. We can't simply say:
        // pointsInexed[index] = newPoint;
        // as then we would have to find old point in unindexed points array and replace it too.
        // Here we use the fact that both points and indexed points arrays keep references to the
        // same objects.
        oldPoint[0] = newPoint[0];
        oldPoint[1] = newPoint[1];
        checkPointsOrder(points);
        pointModified = true;
      }
    }
    if (pointModified) {
      // Recalculate points extent as old point could contain min/max values.
      pointsXMin = pointsYMin = Infinity;
      pointsXMax = pointsYMax = -Infinity;
      pointArray.forEach(function (points) {
        points.forEach(updatePointsExtent);
      });
    }
  }

  // Add an array of points by processing an array of samples (Y values)
  // synthesizing the X value from sampleInterval interval and number of points.
  function addDataSamples(datasamples) {
    var start,
        i;
    if (Object.prototype.toString.call(datasamples[0]) === "[object Array]") {
      for (i = 0; i < datasamples.length; i++) {
        if (!pointArray[i]) { pointArray.push([]); }
        points = pointArray[i];
        start = points.length * sampleInterval + dataSampleStart;
        points.push.apply(points, indexedData(datasamples[i], sampleInterval, start));
        pointArray[i] = points;
        points.forEach(updatePointsExtent);
      }
      points = pointArray[0];
    } else {
      var point;
      for (i = 0; i < datasamples.length; i++) {
        if (!pointArray[i]) { pointArray.push([]); }
        start = pointArray[i].length * sampleInterval + dataSampleStart;
        point = [start, datasamples[i]];
        pointArray[i].push(point);
        updatePointsExtent(point);
      }
    }
  }

  function copyNonNull(array) {
    var ret = [];
    array.forEach(function(element) {
      if (element == null || element[0] == null || element[1] == null) return;
      ret.push(element);
    });
    return ret;
  }

  function copyNonNullKeepIndexing(array) {
    var ret = [];
    array.forEach(function(element, idx) {
      if (element == null || element[0] == null || element[1] == null) return;
      ret[idx] = element;
    });
    return ret;
  }

  // Each points array should be processed:
  // - points extent need to be updated,
  // - points may be sorted if "sortPoints" option is enabled.
  function processPointsArray(array) {
    // Update point extent and check if the points array is sorted by X coordinates.
    function checkPoint(point, idx, array) {
      updatePointsExtent(point);
      if (sorted && idx > 0 && point[0] < array[idx - 1][0]) {
        sorted = false;
      }
    }
    // If options.sortPoints is disabled, we won't executed check in the if statement above.
    var sorted = options.sortPoints;
    array.forEach(checkPoint);
    if (!sorted && options.sortPoints) {
      array.sort(comparePoints);
    }
  }

  function resetDataPoints(datapoints) {

    pointsXMin = pointsYMin =  Infinity;
    pointsXMax = pointsYMax = -Infinity;
    pointArray = [];
    pointArrayIndexed = [];
    if (!datapoints || datapoints.length === 0) {
      pointArray = [[]];
      pointArrayIndexed = [[]];
    } else if (Object.prototype.toString.call(datapoints[0]) === "[object Array]") {
      for (var i = 0; i < datapoints.length; i++) {
        pointArray.push(copyNonNull(datapoints[i]));
        pointArrayIndexed.push(copyNonNullKeepIndexing(datapoints[i]));
        processPointsArray(pointArray[i]);
      }
    } else {
      pointArray = [copyNonNull(points)];
      pointArrayIndexed = [copyNonNullKeepIndexing(points)];
      processPointsArray(pointArray[0]);
    }
    points = pointArray[0];

    autoscale();
    setCurrentSample("last");
  }

  function resetDataSamples(datasamples, interval, start) {
    pointsXMin = pointsYMin = Infinity;
    pointsXMax = pointsYMax = -Infinity;
    pointArray = [];
    if (Object.prototype.toString.call(datasamples[0]) === "[object Array]") {
      for (var i = 0; i < datasamples.length; i++) {
        pointArray.push(indexedData(datasamples[i], interval, start));
        pointArray[pointArray.length-1].forEach(updatePointsExtent);
      }
      points = pointArray[0];
    } else {
      points = indexedData(datasamples, interval, start);
      pointArray = [points];
      points.forEach(updatePointsExtent);
    }
    sampleInterval = interval;
    dataSampleStart = start;
  }


  function resetSamples(datasamples) {
    resetDataSamples(datasamples, sampleInterval, dataSampleStart);
  }

  function deletePoint(pointIndex, arrayIndex) {
    if (!arrayIndex) { arrayIndex = 0; }
    var pointsIndexed = pointArrayIndexed[arrayIndex],
        origPts = points.slice(),
        deleted, newPoints;
    if (pointsIndexed.length) {
      deleted = pointsIndexed[pointIndex].slice();
      pointsIndexed[pointIndex][0] = null;
      pointsIndexed[pointIndex][1] = null;
      pointArrayIndexed[arrayIndex] = pointsIndexed;
      newPoints = copyNonNull(pointsIndexed);
      processPointsArray(newPoints);
      pointArray[arrayIndex] = newPoints;
      if (currentSample >= points.length) {
        currentSample = points.length-1;
      }
    }
    points = pointArray[0];
  }

  // ------------------------------------------------------------
  //
  // Keyboard Handling
  //
  // ------------------------------------------------------------

  function registerKeyboardHandler() {
    svg.node().addEventListener("keydown", function (evt) {
      if (!selected) return false;
      if (evt.type === "keydown") {
        switch (evt.keyCode) {
          case 8:   // backspace
          case 46:  // delete
          if (options.dataChange) {
            var i = points.indexOf(selected);
            deletePoint(i);
            selected = points.length ? points[i > 0 ? i - 1 : 0] : null;
            update();
          }
          evt.preventDefault();
          evt.stopPropagation();
          break;
        }
        evt.preventDefault();
      }
    });
  }

  // ------------------------------------------------------------
  //
  // Graph attribute updaters
  //
  // ------------------------------------------------------------

  // update the title
  function updateTitle() {
    if (options.title && title) {
      title.text(options.title);
    }
    renderGraph();
  }

  // update the x-axis label
  function updateXlabel() {
    if (options.xlabel && xlabel) {
      xlabel.text(options.xlabel);
    }
    renderGraph();
  }

  // update the y-axis label
  function updateYlabel() {
    if (options.ylabel && ylabel) {
      ylabel.text(options.ylabel);
    } else {
      ylabel.style("display", "none");
    }
    renderGraph();
  }

  // ------------------------------------------------------------
  //
  // Main API functions ...
  //
  // ------------------------------------------------------------

  function renderGraph() {
    calculateLayout();
    if (svg === undefined) {
      renderNewGraph();
    } else {
      repaintExistingGraph();
    }
    if (options.showButtons) {
      if (!buttonLayer) createButtonLayer();
    }
    if (options.legendLabels.length > 0) {
      if (!legendLayer) {
        createLegendLayer();
      }
      updateLegendVisibility();
    }
    redraw();
  }

  function reset(idOrElement, options, message) {
    if (arguments.length) {
      initialize(idOrElement, options, message);
    } else {
      initialize();
    }

    // fully reset the buttons, in case which ones are enabled has changed
    if (buttonLayer) {
      buttonLayer.remove();
      buttonLayer = null;
    }

    renderGraph();
    // and then render again using actual size of SVG text elements are
    renderGraph();
    redraw();
    registerKeyboardHandler();
    return api;
  }

  function resize(w, h) {
    scale(w, h);
    initializeLayout();
    renderGraph();
    redraw();
    return api;
  }

  //
  // Public API to instantiated Graph
  //
  api = {
    update:               update,
    repaint:              renderGraph,
    reset:                reset,
    redraw:               redraw,
    resize:               resize,
    notify:               notify,

    // selection brush api
    selectionDomain:      selectionDomain,
    selectionVisible:     selectionVisible,
    selectionListener:    selectionListener,
    selectionEnabled:     selectionEnabled,
    hasSelection:         hasSelection,

    /**
      Read only getter for the d3 selection referencing the DOM elements containing the d3
      brush used to implement selection region manipulation.
    */
    brushElement: function() {
      return brush_element;
    },

    /**
      Read-only getter for the d3 brush control (d3.svg.brush() function) used to implement
      selection region manipulation.
    */
    brushControl: function() {
      return brush_control;
    },

    /**
      Read-only getter for the internal listener to the d3 'brush' event.
    */
    brushListener: function() {
      return brushListener;
    },

    /**
      Allow consumption of points added/removed to graph through clicking
      */
    addPointListener: function(callback) {
      pointListeners.push(callback);
    },

    clearPointListeners: function() {
      pointListeners.length = 0;
    },

    // specific update functions ???
    scale:                scale,
    updateOrRescale:      updateOrRescale,

    xDomain: function(_) {
      if (!arguments.length) return [options.xmin, options.xmax];
      options.xmin = _[0];
      options.xmax = _[1];
      if (updateXScale) {
        updateXScale();
        redraw();
      }
      return api;
    },

    yDomain: function(_) {
      if (!arguments.length) return [options.ymin, options.ymax];
      options.ymin = _[0];
      options.ymax = _[1];
      if (updateYScale) {
        updateYScale();
        redraw();
      }
      return api;
    },

    xmin: function(_) {
      if (!arguments.length) return options.xmin;
      options.xmin = _;
      options.xrange = options.xmax - options.xmin;
      if (updateXScale) {
        updateXScale();
        redraw();
      }
      return api;
    },

    xmax: function(_) {
      if (!arguments.length) return options.xmax;
      options.xmax = _;
      options.xrange = options.xmax - options.xmin;
      if (updateXScale) {
        updateXScale();
        redraw();
      }
      return api;
    },

    ymin: function(_) {
      if (!arguments.length) return options.ymin;
      options.ymin = _;
      options.yrange = options.ymax - options.ymin;
      if (updateYScale) {
        updateYScale();
        redraw();
      }
      return api;
    },

    ymax: function(_) {
      if (!arguments.length) return options.ymax;
      options.ymax = _;
      options.yrange = options.ymax - options.ymin;
      if (updateYScale) {
        updateYScale();
        redraw();
      }
      return api;
    },

    xLabel: function(_) {
      if (!arguments.length) return options.xlabel;
      options.xlabel = _;
      updateXlabel();
      return api;
    },

    yLabel: function(_) {
      if (!arguments.length) return options.ylabel;
      options.ylabel = _;
      updateYlabel();
      return api;
    },

    title: function(_) {
      if (!arguments.length) return options.title;
      options.title = _;
      updateTitle();
      return api;
    },

    width: function(_) {
      if (!arguments.length) return size.width;
      size.width = _;
      return api;
    },

    height: function(_) {
      if (!arguments.length) return size.height;
      size.height = _;
      return api;
    },

    elem: function(_) {
      if (!arguments.length) return elem;
      elem = d3.select(_);
      initialize(elem);
      return api;
    },

    numberOfPoints: function() {
      if (points) {
        return points.length;
      } else {
        return false;
      }
    },

    addAnnotation: function(annotation) {
      annotations.push(annotation);
      redraw();
    },

    resetAnnotations: function() {
      annotations.length = 0;
      redraw();
    },

    // Programmatically the same actions as clicking the autoscale button. Note that we sometimes
    // use autoscale internally with its 'fit' argument set to false.
    autoscale: function() {
      autoscale(true);
    },

    // Point data consist of an array (or arrays) of [x,y] arrays.
    addPoints:     addPoints,
    replacePoints: replacePoints,
    addPoint:      addPoint,
    resetPoints:   resetDataPoints,
    deletePoint:   function(i, idx) {
      deletePoint(i, idx);
      update();
    },

    // Sample data consists of an array (or an array or arrays) of samples.
    // The interval between samples is assumed to have already been set
    // by specifying options.sampleInterval when creating the graph.
    addSamples:    addSamples,
    addSample:     addSample,
    resetSamples:  resetSamples

  };

  // Initialization.
  initialize(idOrElement, options, message);

  if (node) {
    renderGraph();
    // Render again using actual size of SVG text elements.
    renderGraph();
  }

  return api;
};

},{"./axis":1,"./i18n":3}],3:[function(require,module,exports){
var DEFAULT_LANG = 'en-US';

module.exports.translations = require('../locales/translations.json');

module.exports.lang = DEFAULT_LANG;
module.exports.fallback = DEFAULT_LANG;

module.exports.t = function(key) {
  var lang = module.exports.lang;
  return getTranslation(lang, key) ||
         getTranslation(lang.split("-")[0], key) ||
         getTranslation(lang.split("_")[0], key) ||
         getTranslation(module.exports.fallback, key) ||
         key;
};

function getTranslation(lang, key) {
  var translations = module.exports.translations;
  var keys = key.split(".");
  var t = translations[lang];
  var i = 0;
  var k = keys[i];
  while (k && typeof t === "object") {
    t = t[k];
    k = keys[++i];
  }
  return t;
}

},{"../locales/translations.json":4}],4:[function(require,module,exports){
module.exports={
  "en-US": {
    "labels": {
      "autoscale": "Zoom",
      "draw": "Draw",
      "selection": "Select",
      "legend": "Key"
    },
    "tooltips": {
      "autoscale": "Show all data (autoscale)",
      "draw": "Draw new data points",
      "selection": "Select data for export",
      "legend": "Show/hide the legend"
    }
  },
   "it": {
    "labels": {
      "autoscale": "Zoom",
      "draw": "Disegnare",
      "selection": "Selezionare",
      "legend": "Chiave"
    },
    "tooltips": {
      "autoscale": "Mostra tutti i dati (autoscala)",
      "draw": "Disegnare nuovi punti dati",
      "selection": "Seleziona i dati per l'esportazione",
      "legend": "Mostra / nascondi la leggenda"
    }
  },
  "es": {
    "labels": {
      "autoscale": "Zoom",
      "draw": "Graficar",
      "selection": "Elegir",
      "legend": "Leyenda"
    },
    "tooltips": {
      "autoscale": "Mostrar todos los datos (autoescala)",
      "draw": "Graficar nuevos puntos",
      "selection": "Seleccionar datos para exportar",
      "legend": "Mostrar/Ocultar la leyenda"
    }
  },
  "pl": {
    "labels": {
      "autoscale": "Przybliż",
      "draw": "Rysuj",
      "selection": "Zaznacz",
      "legend": "Legenda"
    },
    "tooltips": {
      "autoscale": "Pokaż cały wykres (autoskalowanie)",
      "draw": "Rysuj nowe punkty",
      "selection": "Zaznacz dane do wyeksportowania",
      "legend": "Pokaż/ukryj legendę"
    }
  },
  "ru": {
    "labels": {
      "autoscale": "Масштабировать",
      "draw": "Рисовать",
      "selection": "Выбрать",
      "legend": "Ключ"
    },
    "tooltips": {
      "autoscale": "Показать все данные (автоматическое масштабированиe)",
      "draw": "Показать новые данные",
      "selection": "Выбрать данные для экспорта",
      "legend": "Показать/скрыть описание"
    }
  }
}

},{}],5:[function(require,module,exports){
// Graph constructor.
module.exports = require('./lib/graph');
// Setup access to i18n settings. To use language different from 'en-US', just set:
//   LabGrapher.i18n.lang = "some-language-code";
// before calling Graph constructor.
module.exports.i18n = require('./lib/i18n');

},{"./lib/graph":2,"./lib/i18n":3}]},{},[5])
(5)
});
;
/**
 * @license RequireJS text 2.0.2 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/requirejs/text for details
 */
/*jslint regexp: true */
/*global require: false, XMLHttpRequest: false, ActiveXObject: false,
  define: false, window: false, process: false, Packages: false,
  java: false, location: false */

define('text',['module'], function (module) {
    'use strict';

    var text, fs,
        progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
        xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
        bodyRegExp = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im,
        hasLocation = typeof location !== 'undefined' && location.href,
        defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
        defaultHostName = hasLocation && location.hostname,
        defaultPort = hasLocation && (location.port || undefined),
        buildMap = [],
        masterConfig = (module.config && module.config()) || {};

    text = {
        version: '2.0.2',

        strip: function (content) {
            //Strips <?xml ...?> declarations so that external SVG and XML
            //documents can be added to a document without worry. Also, if the string
            //is an HTML document, only the part inside the body tag is returned.
            if (content) {
                content = content.replace(xmlRegExp, "");
                var matches = content.match(bodyRegExp);
                if (matches) {
                    content = matches[1];
                }
            } else {
                content = "";
            }
            return content;
        },

        jsEscape: function (content) {
            return content.replace(/(['\\])/g, '\\$1')
                .replace(/[\f]/g, "\\f")
                .replace(/[\b]/g, "\\b")
                .replace(/[\n]/g, "\\n")
                .replace(/[\t]/g, "\\t")
                .replace(/[\r]/g, "\\r")
                .replace(/[\u2028]/g, "\\u2028")
                .replace(/[\u2029]/g, "\\u2029");
        },

        createXhr: masterConfig.createXhr || function () {
            //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
            var xhr, i, progId;
            if (typeof XMLHttpRequest !== "undefined") {
                return new XMLHttpRequest();
            } else if (typeof ActiveXObject !== "undefined") {
                for (i = 0; i < 3; i += 1) {
                    progId = progIds[i];
                    try {
                        xhr = new ActiveXObject(progId);
                    } catch (e) {}

                    if (xhr) {
                        progIds = [progId];  // so faster next time
                        break;
                    }
                }
            }

            return xhr;
        },

        /**
         * Parses a resource name into its component parts. Resource names
         * look like: module/name.ext!strip, where the !strip part is
         * optional.
         * @param {String} name the resource name
         * @returns {Object} with properties "moduleName", "ext" and "strip"
         * where strip is a boolean.
         */
        parseName: function (name) {
            var strip = false, index = name.indexOf("."),
                modName = name.substring(0, index),
                ext = name.substring(index + 1, name.length);

            index = ext.indexOf("!");
            if (index !== -1) {
                //Pull off the strip arg.
                strip = ext.substring(index + 1, ext.length);
                strip = strip === "strip";
                ext = ext.substring(0, index);
            }

            return {
                moduleName: modName,
                ext: ext,
                strip: strip
            };
        },

        xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,

        /**
         * Is an URL on another domain. Only works for browser use, returns
         * false in non-browser environments. Only used to know if an
         * optimized .js version of a text resource should be loaded
         * instead.
         * @param {String} url
         * @returns Boolean
         */
        useXhr: function (url, protocol, hostname, port) {
            var uProtocol, uHostName, uPort,
                match = text.xdRegExp.exec(url);
            if (!match) {
                return true;
            }
            uProtocol = match[2];
            uHostName = match[3];

            uHostName = uHostName.split(':');
            uPort = uHostName[1];
            uHostName = uHostName[0];

            return (!uProtocol || uProtocol === protocol) &&
                   (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
                   ((!uPort && !uHostName) || uPort === port);
        },

        finishLoad: function (name, strip, content, onLoad) {
            content = strip ? text.strip(content) : content;
            if (masterConfig.isBuild) {
                buildMap[name] = content;
            }
            onLoad(content);
        },

        load: function (name, req, onLoad, config) {
            //Name has format: some.module.filext!strip
            //The strip part is optional.
            //if strip is present, then that means only get the string contents
            //inside a body tag in an HTML string. For XML/SVG content it means
            //removing the <?xml ...?> declarations so the content can be inserted
            //into the current doc without problems.

            // Do not bother with the work if a build and text will
            // not be inlined.
            if (config.isBuild && !config.inlineText) {
                onLoad();
                return;
            }

            masterConfig.isBuild = config.isBuild;

            var parsed = text.parseName(name),
                nonStripName = parsed.moduleName + '.' + parsed.ext,
                url = req.toUrl(nonStripName),
                useXhr = (masterConfig.useXhr) ||
                         text.useXhr;

            //Load the text. Use XHR if possible and in a browser.
            if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {
                text.get(url, function (content) {
                    text.finishLoad(name, parsed.strip, content, onLoad);
                }, function (err) {
                    if (onLoad.error) {
                        onLoad.error(err);
                    }
                });
            } else {
                //Need to fetch the resource across domains. Assume
                //the resource has been optimized into a JS module. Fetch
                //by the module name + extension, but do not include the
                //!strip part to avoid file system issues.
                req([nonStripName], function (content) {
                    text.finishLoad(parsed.moduleName + '.' + parsed.ext,
                                    parsed.strip, content, onLoad);
                });
            }
        },

        write: function (pluginName, moduleName, write, config) {
            if (buildMap.hasOwnProperty(moduleName)) {
                var content = text.jsEscape(buildMap[moduleName]);
                write.asModule(pluginName + "!" + moduleName,
                               "define(function () { return '" +
                                   content +
                               "';});\n");
            }
        },

        writeFile: function (pluginName, moduleName, req, write, config) {
            var parsed = text.parseName(moduleName),
                nonStripName = parsed.moduleName + '.' + parsed.ext,
                //Use a '.js' file name so that it indicates it is a
                //script that can be loaded across domains.
                fileName = req.toUrl(parsed.moduleName + '.' +
                                     parsed.ext) + '.js';

            //Leverage own load() method to load plugin value, but only
            //write out values that do not have the strip argument,
            //to avoid any potential issues with ! in file names.
            text.load(nonStripName, req, function (value) {
                //Use own write() method to construct full module value.
                //But need to create shell that translates writeFile's
                //write() to the right interface.
                var textWrite = function (contents) {
                    return write(fileName, contents);
                };
                textWrite.asModule = function (moduleName, contents) {
                    return write.asModule(moduleName, fileName, contents);
                };

                text.write(pluginName, nonStripName, textWrite, config);
            }, config);
        }
    };

    if (typeof process !== "undefined" &&
             process.versions &&
             !!process.versions.node) {
        //Using special require.nodeRequire, something added by r.js.
        fs = require.nodeRequire('fs');

        text.get = function (url, callback) {
            var file = fs.readFileSync(url, 'utf8');
            //Remove BOM (Byte Mark Order) from utf8 files if it is there.
            if (file.indexOf('\uFEFF') === 0) {
                file = file.substring(1);
            }
            callback(file);
        };
    } else if (typeof Packages !== 'undefined' && typeof java !== 'undefined') {
        //Why Java, why is this so awkward?
        text.get = function (url, callback) {
            var stringBuffer, line,
                encoding = "utf-8",
                file = new java.io.File(url),
                lineSeparator = java.lang.System.getProperty("line.separator"),
                input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),
                content = '';
            try {
                stringBuffer = new java.lang.StringBuffer();
                line = input.readLine();

                // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
                // http://www.unicode.org/faq/utf_bom.html

                // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
                // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
                if (line && line.length() && line.charAt(0) === 0xfeff) {
                    // Eat the BOM, since we've already found the encoding on this file,
                    // and we plan to concatenating this buffer with others; the BOM should
                    // only appear at the top of a file.
                    line = line.substring(1);
                }

                stringBuffer.append(line);

                while ((line = input.readLine()) !== null) {
                    stringBuffer.append(lineSeparator);
                    stringBuffer.append(line);
                }
                //Make sure we return a JavaScript string and not a Java string.
                content = String(stringBuffer.toString()); //String
            } finally {
                input.close();
            }
            callback(content);
        };
    } else if (text.createXhr()) {
        text.get = function (url, callback, errback) {
            var xhr = text.createXhr();
            xhr.open('GET', url, true);

            //Allow overrides specified in config
            if (masterConfig.onXhr) {
                masterConfig.onXhr(xhr, url);
            }

            xhr.onreadystatechange = function (evt) {
                var status, err;
                //Do not explicitly handle errors, those should be
                //visible via console output in the browser.
                if (xhr.readyState === 4) {
                    status = xhr.status;
                    if (status > 399 && status < 600) {
                        //An http 4xx or 5xx error. Signal an error.
                        err = new Error(url + ' HTTP status: ' + status);
                        err.xhr = xhr;
                        errback(err);
                    } else {
                        callback(xhr.responseText);
                    }
                }
            };
            xhr.send(null);
        };
    }

    return text;
});


define('text!locales/translations.json',[],function () { return '{\n  "en-US": {\n    "translation": {\n      "banner": {\n        "about": "About",\n        "about_tooltip": "Instructions",\n        "share": "Share",\n        "share_tooltip": "Share using e-mail, IM or embed in website",\n        "lang_tooltip": "Select language",\n        "reload_tooltip": "Reload interactive",\n        "help_tooltip": "Show help tips",\n        "credits_tooltip": "Learn more about The Concord Consortium",\n        "fullscreen_tooltip": "Toggle full-screen",\n        "video_play_pause_tooltip": "Start / pause the simulation",\n        "video_reset_tooltip": "Reset the simulation",\n        "video_step_back_tooltip": "Step back",\n        "video_step_forward_tooltip": "Step forward",\n        "text_start": "Start",\n        "text_start_tooltip": "Start the simulation or data collection",\n        "text_stop": "Stop",\n        "text_stop_tooltip": "Stop the simulation or data collection",\n        "text_reset": "Reset",\n        "text_reset_tooltip": "Reset the simulation or data collection",\n        "text_new_run": "New Run",\n        "text_new_run_tooltip": "Set up a new experiment run",\n        "text_analyze_data": "Analyze Data",\n        "text_analyze_data_tooltip": "Send data from the experiment to CODAP"\n      },\n      "dialog": {\n        "close_tooltip": "Close"\n      },\n      "about_dialog": {\n        "title": "About: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "Share: __interactive_title__",\n        "link": "link",\n        "paste_email_im": "Paste this __link__ in email or IM.",\n        "paste_html": "Paste HTML to embed in website or blog.",\n        "select_size": "Select Size:",\n        "size_larger": "__val__% larger",\n        "size_actual": "actual",\n        "size_smaller": "__val__% smaller"\n      },\n      "credits_dialog": {\n        "title": "Credits: __interactive_title__",\n        "credits_text": "This interactive was created by the __CC_link__ using our __Next_Gen_MW_link__ software, with funding by a grant from __Google_link__.",\n        "shareable_ver": "shareable version",\n        "find_shareable": "Find a __shareable_ver_link__ of this interactive along with dozens of other open-source interactives for science, math and engineering at __concord_org_link__."\n      },\n      "copyright": {\n        "copyright": "Copyright",\n        "all_rights_reserved": "All rights reserved.",\n        "license": "The software is licensed under the __MIT_link__ license. Please see __license_link__ for other software and associated licensing included in this product.",\n        "attribution": "Please provide attribution to the Concord Consortium and the URL __concord_org_link__."\n      },\n      "md2d": {\n        "heatbath_icon_tooltip": "Heatbath active",\n        "ke_icon_tooltip": "Kinetic energy gradient",\n        "invalid_object_position_alert": "You can\'t drop the object there.",\n        "aminoacid_menu": {\n          "hydrophobic": "Hydrophobic",\n          "hydrophilic": "Hydrophilic",\n          "glycine": "Glycine",\n          "alanine": "Alanine",\n          "valine": "Valine",\n          "leucine": "Leucine",\n          "isoleucine": "Isoleucine",\n          "phenylalanine": "Phenylalanine",\n          "proline": "Proline",\n          "tryptophan": "Tryptophan",\n          "methionine": "Methionine",\n          "cysteine": "Cysteine",\n          "tyrosine": "Tyrosine",\n          "asparagine": "Asparagine",\n          "glutamine": "Glutamine",\n          "serine": "Serine",\n          "threonine": "Threonine",\n          "asparticacid": "Aspartic acid",\n          "glutamicacid": "Glutamic acid",\n          "lysine": "Lysine",\n          "arginine": "Arginine",\n          "histidine": "Histidine"\n        },\n        "mutations_menu": {\n          "substitution_mutation": "Substitution mutation",\n          "insertion_mutation": "Insertion mutation",\n          "deletion_mutation": "Deletion mutation",\n          "insert": "Insert"\n        }\n      },\n      "sensor": {\n        "select_sensor": "Select Sensor",\n        "select_sensor_type": "Select type of sensor...",\n        "reading": "Reading:",\n        "zero": "Zero",\n        "zeroing": "Zeroing...",\n        "loading_sensor": "Loading sensor...",\n        "choose_sensor_title": "Select a sensor:",\n        "connect": "Connect",\n        "address_labquest2": "address of LabQuest2",\n        "messages": {\n          "ready": "Ready to collect.",\n          "ready_nocontrol": "Please stop the __controlling_client__ data collector to collect data here.",\n          "ready_nocontrol_noname": "Please stop the other active data collector to collect data here.",\n          "no_sensors": "No sensors found.",\n          "no_devices": "No devices plugged in.",\n          "not_connected": "Not connected.",\n          "connecting": "Connecting...",\n          "connection_in_progress": "Connecting to your sensors. If a message comes up about sensorconnector.concord.org, please accept it.",\n          "connection_failed": "Connection failed. __retry_link__",\n          "connection_failed_retry_link_text": "Try again",\n          "connection_failed_alert": "The Concord Consortium Sensor Connector is not installed or is not running. Please __click_here_link__ for instructions on using the Sensor Connector.",\n          "connection_failed_labquest2_alert": "Could not connect to the LabQuest2. Please make sure the address is correct and that the LabQuest2 can be reached from this computer",\n          "tare_labquest2_alert": "The LabQuest2 needs to be collecting live data in order to zero. Either set up a new run on the LabQuest2, or click the meter icon in the upper left.",\n          "click_here": "click here",\n          "connected": "Connected.",\n          "connected_start_labquest2": "Connected. Press start on your LabQuest2 to begin.",\n          "connected_start_sensorconnector": "Please stop the __controlling_client__ data collector to collect data here.",\n          "connected_start_sensorconnector_noname": "Please stop the other active data collector to collect data here.",\n          "starting_data_collection": "Starting data collection...",\n          "error_starting_data_collection": "Error starting data collection.",\n          "error_starting_data_collection_alert": "Could not start data collection. Make sure that (remote starting) is enabled",\n          "collecting_data": "Collecting data.",\n          "collecting_data_stop_labquest2": "Collecting data. Press stop on your LabQuest2 to end.",\n          "collecting_data_stop_sensorconnector": "Collecting data.",\n          "no_data": "No data is available.",\n          "no_data_alert": "The Sensor Connector does not appear to be reporting data for the plugged-in device",\n          "no_data_labquest2_alert": "The LabQuest does not appear to be reporting data for the plugged-in device",\n          "canceling_data_collection": "Canceling data collection...",\n          "error_canceling_data_collection": "Error canceling data collection.",\n          "error_canceling_data_collection_alert": "Could not cancel data collection. Make sure that (remote starting) is enabled",\n          "stopping_data_collection": "Stopping data collection...",\n          "error_stopping_data_collection": "Error stopping data collection.",\n          "error_stopping_data_collection_alert": "Could not stop data collection. Make sure that (remote starting) is enabled",\n          "data_collection_stopped": "Data collection stopped.",\n          "data_collection_complete": "Data collection complete.",\n          "disconnected": "Disconnected.",\n          "java_applet_error": "It appears that Java applets cannot run in your browser. If you are able to fix this, reload the page to use the sensor",\n          "java_applet_not_loading": "The sensor applet appears not to be loading. If you are able to fix this, reload the page to use the sensor",\n          "unexpected_error": "There was an unexpected error when connecting to the sensor.",\n          "sensor_not_attached": "The __sensor_name__ does not appear to be attached. Try re-attaching it, and then click \\"$t(sensor.messages.try_again)\\".",\n          "sensor_or_device_unplugged": "The __sensor_or_device_name__ was unplugged. Try plugging it back in, and then click \\"$t(sensor.messages.try_again)\\".",\n          "try_again": "Try Again",\n          "cancel": "Cancel"\n        },\n        "measurements": {\n          "sensor_reading": "Sensor Reading",\n          "time": "Time",\n          "distance": "Distance",\n          "acceleration": "Acceleration",\n          "altitude": "Altitude",\n          "angle": "Angle",\n          "CO2": "CO₂",\n          "CO2_concentration": "CO₂ Concentration",\n          "charge": "Charge",\n          "conductivity": "Conductivity",\n          "current": "Current",\n          "dissolved_oxygen": "DO",\n          "flow_rate": "Flow Rate",\n          "fluorescence_405_nm": "Fluorescence 405 nm",\n          "fluorescence_500_nm": "Fluorescence 500 nm",\n          "force": "Force",\n          "intensity": "Intensity",\n          "light_level": "Light Level",\n          "light_intensity": "Light Intensity",\n          "magnetic_field": "Magnetic Field",\n          "position": "Position",\n          "potential": "Potential",\n          "pressure": "Pressure",\n          "signal": "Signal",\n          "sound_level": "Sound Level",\n          "speed": "Speed",\n          "temperature": "Temperature",\n          "transmittance": "Transmittance",\n          "turbidity": "Turbidity",\n          "UV_intensity": "UV Intensity",\n          "velocity": "Velocity",\n          "volume": "Volume",\n          "pH": "pH",\n          "acidity": "Acidity",\n          "O2_concentration": "O₂ Concentration"\n        },\n        "names": {\n          "sensor": "sensor",\n          "no_sensor": "(no sensor)",\n          "light": "Light",\n          "motion": "Motion",\n          "accelerometer": "Accelerometer",\n          "dissolved_oxygen": "Dissolved Oxygen",\n          "pressure": "Pressure",\n          "charge_sensor": "Charge Sensor",\n          "voltage": "Voltage",\n          "pH": "pH",\n          "CO2_gas": "CO₂ Gas",\n          "colorimeter": "Colorimeter",\n          "conductivity": "Conductivity",\n          "current": "Current",\n          "temperature": "Temperature",\n          "force": "Force",\n          "anemometer": "Anemometer",\n          "hand_dynamometer": "Hand Dynamometer",\n          "heart_rate": "Heart Rate",\n          "magnetic_field": "Magnetic Field",\n          "rotary_motion": "Rotary Motion",\n          "linear_position_sensor": "Linear Position Sensor",\n          "sound_level": "Sound Level",\n          "spectrophotometer": "Spectrophotometer",\n          "spirometer": "Spirometer",\n          "turbidity": "Turbidity",\n          "UV_sensor": "UV Sensor",\n          "drop_counter": "Drop Counter",\n          "altitude": "Altitude",\n          "goMotion": "GoMotion",\n          "goTemp": "GoIO Temperature Sensor",\n          "goLinkTemperature": "GoIO Temperature Sensor",\n          "goLinkLight": "GoIO Light Sensor",\n          "goLinkForce": "GoIO Force Sensor",\n          "goLinkPH": "GoIO pH Sensor",\n          "goLinkCO2": "GoIO CO₂ sensor",\n          "goLinkO2": "GoIO O₂ sensor",\n          "labQuestMotion": "LabQuest Motion Sensor",\n          "labQuestTemperature": "LabQuest Temperature Sensor",\n          "labQuestLight": "LabQuest Light Sensor",\n          "labQuestForce": "LabQuest Force Sensor",\n          "labQuestPH": "LabQuest pH Sensor",\n          "labQuestCO2": "LabQuest CO₂ sensor",\n          "labQuestO2": "LabQuest O₂ sensor"\n        }\n      }\n    }\n  },\n  \n  "el": {\n    "translation": {\n      "banner": {\n        "about": "Πληροφορίες",\n        "about_tooltip": "Οδηγίες",\n        "share": "Κοινοποίηση",\n        "share_tooltip": "Κοινοποίηση με e-mail, μήνυμα ή ενσωμάτωση σε ιστότοπο",\n        "lang_tooltip": "Επιλογή γλώσσας",\n        "reload_tooltip": "Επαναφόρτωση διαδραστικού",\n        "help_tooltip": "Εμφάνιση συμβουλών",\n        "credits_tooltip": "Μάθε περισσότερα για το Concord Consortium",\n        "fullscreen_tooltip": "Εναλλαγή πλήρους οθόνης",\n        "video_play_pause_tooltip": "Έναρξη / παύση της προσομοίωσης",\n        "video_reset_tooltip": "Επαναφορά της προσομοίωσης",\n        "video_step_back_tooltip": "Βήμα πίσω",\n        "video_step_forward_tooltip": "Βήμα εμπρός",\n        "text_start": "Εκτέλεση",\n        "text_start_tooltip": "Έναρξη της προσομοίωσης ή συλλογή δεδομένων",\n        "text_stop": "Σταμάτημα",\n        "text_stop_tooltip": "Σταμάτημα της προσομοίωσης ή συλλογή δεδομένων",\n        "text_reset": "Επαναφορά",\n        "text_reset_tooltip": "Επαναφορά της προσομοίωσης ή συλλογή δεδομένων",\n        "text_new_run": "Νέα εκτέλεση",\n        "text_new_run_tooltip": "Ρύθμιση μιας νέας εκτέλεσης πειράματος",\n        "text_analyze_data": "Ανάλυση δεδομένων",\n        "text_analyze_data_tooltip": "Αποστολή δεδομένων από το πείραμα στο CODAP"\n      },\n      "dialog": {\n        "close_tooltip": "Κλείσιμο"\n      },\n      "about_dialog": {\n        "title": "Πληροφορίες: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "Κοινοποίηση: __interactive_title__",\n        "link": "συνδέσμου",\n        "paste_email_im": "Επικόλληση αυτού του __link__ σε email ή μήνυμα.",\n        "paste_html": "Επικόλληση HTML για ενσωμάτωση σε ιστότοπο ή ιστολόγιο.",\n        "select_size": "Επιλογή μεγέθους:",\n        "size_larger": "__val__% μεγαλύτερο",\n        "size_actual": "πραγματικό",\n        "size_smaller": "__val__% μικρότερο"\n      },\n      "credits_dialog": {\n        "title": "Παράγοντες: __interactive_title__",\n        "credits_text": "Αυτό το διαδραστικό δημιουργήθηκε από __CC_link__ χρησιμοποιώντας το λογισμικό __Next_Gen_MW_link__, με χρηματοδότηση μέσω μιας δωρεάς από __Google_link__.",\n        "shareable_ver": "κοινοποιήσιμη έκδοση",\n        "find_shareable": "Εύρεση μιας __shareable_ver_link__ αυτού του διαδραστικού μαζί με δεκάδες άλλων διαδραστικών ανοικτού κώδικα για επιστήμη, μαθηματικά και μηχανική στο __concord_σrg_link__."\n      },\n      "copyright": {\n        "copyright": "Πνευματικά δικαιώματα",\n        "all_rights_reserved": "Όλα τα δικαιώματα κατοχυρωμένα.",\n        "license": "Το λογισμικό είναι αδειοδοτημένο κάτω από άδεια __MIT_link__.",\n        "attribution": "Παρακαλώ αναφέρετε το Concord Consortium και το URL __concord_org_link__."\n      },\n      "md2d": {\n        "heatbath_icon_tooltip": "Λουτρό θερμότητας ενεργό",\n        "ke_icon_tooltip": "Κλίση κινητικής ενέργειας",\n        "invalid_object_position_alert": "Δεν μπορείτε να ρίξετε το αντικείμενο εκεί.",\n        "aminoacid_menu": {\n          "hydrophobic": "Υδρόφοβο",\n          "hydrophilic": "Υδρόφιλο",\n          "glycine": "Γλυκίνη",\n          "alanine": "Αλανίνη",\n          "valine": "Βαλίνη",\n          "leucine": "Λευκίνη",\n          "isoleucine": "Ισολευκίνη",\n          "phenylalanine": "Φαινυλαλανίνη",\n          "proline": "Προλίνη",\n          "tryptophan": "Τρυπτοφάνη",\n          "methionine": "Μεθειονίνη",\n          "cysteine": "Κυστεΐνη",\n          "tyrosine": "Τυροσίνη",\n          "asparagine": "Ασπαραγίνη",\n          "glutamine": "Γλουταμίνη",\n          "serine": "Σερίνη",\n          "threonine": "Θρεονίνη",\n          "asparticacid": "Ασπαρτικό οξύ",\n          "glutamicacid": "Γλουταμινικό οξύ",\n          "lysine": "Λυσίνη",\n          "arginine": "Αργινίνη",\n          "histidine": "Ιστιδίνη"\n        },\n        "mutations_menu": {\n          "substitution_mutation": "Μετάλλαξη αντικατάστασης",\n          "insertion_mutation": "Μετάλλαξη προσθήκης",\n          "deletion_mutation": "Μετάλλαξη αφαίρεσης",\n          "insert": "Εισαγωγή"\n        }\n      },\n      "sensor": {\n        "select_sensor": "Επιλογή αισθητήρα",\n        "select_sensor_type": "Επιλογή τύπου αισθητήρα...",\n        "reading": "Ανάγνωση:",\n        "zero": "Μηδέν",\n        "zeroing": "Μηδενισμός...",\n        "loading_sensor": "Φόρτωμα αισθητήρα...",\n        "choose_sensor_title": "Επιλογή αισθητήρα:",\n        "connect": "Σύνδεση",\n        "address_labquest2": "διεύθυνση του LabQuest2",\n        "messages": {\n          "ready": "Έτοιμο για συλλογή.",\n          "ready_nocontrol": "Παρακαλώ σταματήστε το συλλέκτη δεδομένων __controlling_client__ για να συλλέξετε δεδομένα εδώ.",\n          "ready_nocontrol_noname": "Παρακαλώ σταματήστε τον άλλο ενεργό συλλέκτη δεδομένων για να συλλέξετε δεδομένα εδώ.",\n          "no_sensors": "Δε βρέθηκαν αισθητήρες.",\n          "no_devices": "Δεν υπάρχουν συνδεδεμένες συσκευές.",\n          "not_connected": "Μη συνδεδεμένο.",\n          "connecting": "Σύνδεση...",\n          "connection_in_progress": "Γίνετε σύνδεση στους αισθητήρες σας. Αν εμφανιστεί ένα μήνυμα για το sensorconnector.concord.org, παρακαλώ αποδεχτείτε το.",\n          "connection_failed": "Η σύνδεση απέτυχε. __retry_link__",\n          "connection_failed_retry_link_text": "Προσπαθήστε ξανά",\n          "connection_failed_alert": "Το Concord Consortium Sensor Connector δεν είναι εγκατεστημένο ή δεν εκτελείται. Παρακαλώ __click_here_link__ για οδηγίες χρήσης του Sensor Connector.",\n          "connection_failed_labquest2_alert": "Δεν ήταν δυνατό να γίνει σύνδεση στο LabQuest2. Παρακαλώ σιγουρευτείτε πως η διεύθυνση είναι σωστή και πως το LabQuest2 μπορεί να προσπελαστεί από αυτόν τον υπολογιστή",\n          "tare_labquest2_alert": "Το LabQuest2 χρειάζεται να συλλέγει ζωντανά δεδομένα για να μηδενιστεί. Είτε ρυθμίστε μια νέα εκτέλεση στο LabQuest2, ή κάντε κλικ στο εικονίδιο μετρητή πάνω αριστερά.",\n          "click_here": "πατήστε εδώ",\n          "connected": "Συνδεδεμένο.",\n          "connected_start_labquest2": "Συνδεδεμένο. Πατήστε έναρξη στο LabQuest2 για έναρξη.",\n          "connected_start_sensorconnector": "Παρακαλώ σταματήστε το συλλέκτη δεδομένων __controlling_client__ για να συλλέξετε δεδομένα εδώ.",\n          "connected_start_sensorconnector_noname": "Παρακαλώ σταματήστε τον άλλο ενεργό συλλέκτη δεδομένων για να συλλέξετε δεδομένα εδώ.",\n          "starting_data_collection": "Έναρξη συλλογής δεδομένων...",\n          "error_starting_data_collection": "Σφάλμα έναρξης συλλογής δεδομένων.",\n          "error_starting_data_collection_alert": "Δεν ήταν δυνατή η έναρξη συλλογής δεδομένων. Σιγουρευτείτε πως η (απομακρυσμένη έναρξη) είναι ενεργοποιημένη",\n          "collecting_data": "Συλλογή δεδομένων.",\n          "collecting_data_stop_labquest2": "Συλλογή δεδομένων. Πατήστε σταμάτημα στο LabQuest2 για τερματισμό.",\n          "collecting_data_stop_sensorconnector": "Συλλογή δεδομένων.",\n          "no_data": "Δεν υπάρχουν διαθέσιμα δεδομένα.",\n          "no_data_alert": "Το Sensor Connector δε φαίνεται να αναφέρει δεδομένα για τη συνδεδεμένη συσκευή",\n          "no_data_labquest2_alert": "Το LabQuest δε φαίνεται να αναφέρει δεδομένα για τη συνδεδεμένη συσκευή",\n          "canceling_data_collection": "Ακύρωση συλλογής δεδομένων...",\n          "error_canceling_data_collection": "Σφάλμα ακύρωσης συλλογής δεδομένων.",\n          "error_canceling_data_collection_alert": "Δεν ήταν δυνατή η ακύρωση συλλογής δεδομένων. Σιγουρευτείτε πως η (απομακρυσμένη εκκίνηση) είναι ενεργοποιημένη",\n          "stopping_data_collection": "Τερματισμός συλλογής δεδομένων...",\n          "error_stopping_data_collection": "Σφάλμα τερματισμού συλλογής δεδομένων.",\n          "error_stopping_data_collection_alert": "Δεν ήταν δυνατός ο τερματισμός συλλογής δεδομένων. Σιγουτευτείτε πως η (απομακρυσμένη συλλογή δεδομένων) είναι ενεργοποιημένη",\n          "data_collection_stopped": "Η συλλογή δεδομένων τερματίστηκε.",\n          "data_collection_complete": "Η συλλογή δεδομένων ολοκληρώθηκε.",\n          "disconnected": "Αποσυνδεδεμένο.",\n          "java_applet_error": "Φαίνεται πως δεν επιτρέπεται εκτέλεση Java applet στο φυλλομετρητή σας. Αν μπορείτε να το διορθώσετε, επαναφορτώστε τη σελίδα για να χρησιμοποιήσετε τον αισθητήρα",\n          "java_applet_not_loading": "Το applet του αισθητήρα φαίνεται να μη φορτώνεται. Αν μπορείτε να το διορθώσετε, επαναφορτώστε τη σελίδα για να χρησιμοποιήσετε τον αισθητήρα",\n          "unexpected_error": "Υπήρξε ένα μη αναμενόμενο σφάλμα κατά τη σύνδεση στον αισθητήρα.",\n          "sensor_not_attached": "Το __sensor_name__ does δεν φαίνεται να είναι συνδεδεμένο. Δοκιμάστε να το επανασυνδέσετε, και έπειτα πατήστε \\"$t(sensor.messages.try_again)\\".",\n          "sensor_or_device_unplugged": "Το __sensor_or_device_name__ αποσυνδέθηκε. Δοκιμάστε να το επανασυνδέσετε, και έπειτα πατήστε \\"$t(sensor.messages.try_again)\\".",\n          "try_again": "Προσπαθήστε ξανά",\n          "cancel": "Άκυρο"\n        },\n        "measurements": {\n          "sensor_reading": "Μέτρηση αισθητήρα",\n          "time": "Χρόνος",\n          "distance": "Απόσταση",\n          "acceleration": "Επιτάχυνση",\n          "altitude": "Υψόμετρο",\n          "angle": "Γωνία",\n          "CO2": "CO₂",\n          "CO2_concentration": "Συγκέντρωση CO₂",\n          "charge": "Φορτίο",\n          "conductivity": "Αγωγιμότητα",\n          "current": "Ρεύμα",\n          "dissolved_oxygen": "DO (διαλυμένο Οξυγόνο)",\n          "flow_rate": "Ρυθμός ροής",\n          "fluorescence_405_nm": "Φθορισμός 405 nm",\n          "fluorescence_500_nm": "Φθορισμός 500 nm",\n          "force": "Δύναμη",\n          "intensity": "Ένταση",\n          "light_level": "Επίπεδο φωτός",\n          "light_intensity": "Ένταση φωτός",\n          "magnetic_field": "Μαγνητικό πεδίο",\n          "position": "Θέση",\n          "potential": "Δυναμικό",\n          "pressure": "Πίεση",\n          "signal": "Σήμα",\n          "sound_level": "Επίπεδο ήχου",\n          "speed": "Ταχύτητα",\n          "temperature": "Θερμοκρασία",\n          "transmittance": "Μεταδοτικότητα",\n          "turbidity": "Θολότητα",\n          "UV_intensity": "Ένταση UV",\n          "velocity": "Διανυσματική ταχύτητα",\n          "volume": "Volume",\n          "pH": "pH",\n          "acidity": "Οξύτητα",\n          "O2_concentration": "Συγκέντρωση O₂"\n        },\n        "names": {\n          "sensor": "αισθητήρας",\n          "no_sensor": "(χωρίς αισθητήρα)",\n          "light": "Φώς",\n          "motion": "Κίνηση",\n          "accelerometer": "Επιταχυνσιόμετρο",\n          "dissolved_oxygen": "Διαλυμένο Οξυγόνο",\n          "pressure": "Πίεση",\n          "charge_sensor": "Αισθητήρας φορτίου",\n          "voltage": "Τάση",\n          "pH": "pH",\n          "CO2_gas": "Αέριο CO₂",\n          "colorimeter": "Χρωματόμετρο",\n          "conductivity": "Αγωγιμότητα",\n          "current": "Ρεύμα",\n          "temperature": "Θερμοκρασία",\n          "force": "Δύναμη",\n          "anemometer": "Ανεμόμετρο",\n          "hand_dynamometer": "Δυναμόμετρο χειρός",\n          "heart_rate": "Καρδιακός ρυθμός",\n          "magnetic_field": "Μαγνητικό πεδίο",\n          "rotary_motion": "Περιστροφική κίνηση",\n          "linear_position_sensor": "Αισθητήρας γραμικής θέσης",\n          "sound_level": "Επίπεδο ήχου",\n          "spectrophotometer": "Φασματοφωτόμετρο",\n          "spirometer": "Σπιρόμετρο",\n          "turbidity": "Θολότητα",\n          "UV_sensor": "Αισθητήρας UV",\n          "drop_counter": "Μετρητής πτώσης",\n          "altitude": "Υψόμετρο",\n          "goMotion": "GoMotion",\n          "goTemp": "GoIO αισθητήρας θερμοκρασίας",\n          "goLinkTemperature": "GoIO αισθητήρας θερμοκρασίας",\n          "goLinkLight": "GoIO αισθητήρας φωτός",\n          "goLinkForce": "GoIO αισθητήρας δύναμης",\n          "goLinkPH": "GoIO αισθητήρας pH",\n          "goLinkCO2": "GoIO αισθητήρας CO₂",\n          "goLinkO2": "GoIO αισθητήρας O₂",\n          "labQuestMotion": "LabQuest αισθητήρας κίνησης",\n          "labQuestTemperature": "LabQuest αισθητήρας θερμοκρασίας",\n          "labQuestLight": "LabQuest αισθητήρας φωτός",\n          "labQuestForce": "LabQuest αισθητήρας δύναμης",\n          "labQuestPH": "LabQuest αισθητήρας pH",\n          "labQuestCO2": "LabQuest αισθητήρας CO₂",\n          "labQuestO2": "LabQuest αισθητήρας O₂"\n        }\n      }\n    }\n  },\n  \n  "it": {\n    "translation": {\n      "banner": {\n        "about": "Chi siamo",\n        "about_tooltip": "Istruzioni",\n        "share": "Condividere",\n        "share_tooltip": "Condividi tramite e-mail, IM o incorporato nel sito web",\n        "lang_tooltip": "Seleziona la lingua",\n        "reload_tooltip": "Ricarica interattiva",\n        "help_tooltip": "Mostra suggerimenti di aiuto",\n        "credits_tooltip": "Scopri di più su The Concord Consortium",\n        "fullscreen_tooltip": "Passare a schermo intero",\n        "video_play_pause_tooltip": "Avviare / mettere in pausa la simulazione",\n        "video_reset_tooltip": "Ripristina la simulazione",\n        "video_step_back_tooltip": "Fai un passo indietro",\n        "video_step_forward_tooltip": "Passo in avanti",\n        "text_start": "Inizio",\n        "text_start_tooltip": "Avviare la simulazione o la raccolta di dati",\n        "text_stop": "Stop",\n        "text_stop_tooltip": "Interrompere la simulazione o la raccolta dei dati",\n        "text_reset": "Reset",\n        "text_reset_tooltip": "Ripristina la simulazione o la raccolta di dati",\n        "text_new_run": "Nuova corsa",\n        "text_new_run_tooltip": "Imposta un nuovo run di esperimenti",\n        "text_analyze_data": "Analizzare i dati",\n        "text_analyze_data_tooltip": "Invia dati dall\'esperimento a CODAP"\n      },\n      "dialog": {\n        "close_tooltip": "Chiudere"\n      },\n      "about_dialog": {\n        "title": "Di: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "Condividere: __interactive_title__",\n        "link": "link",\n        "paste_email_im": "Incolla questo __link__ in posta elettronica o IM.",\n        "paste_html": "Incolla HTML per incorporare in sito o blog.",\n        "select_size": "Seleziona la dimensione:",\n        "size_larger": "__val__% più grandi",\n        "size_actual": "effettivo",\n        "size_smaller": "__val__% più piccola"\n      },\n      "credits_dialog": {\n        "title": "Crediti: __interactive_title__",\n        "credits_text": "Questo interattivo è stato creato da __CC_link__ usando il nostru __Next_Gen_MW_link__ software, con finanziamento da una sovvenzione di __Google_link__.",\n        "shareable_ver": "versione condivisibile",\n        "find_shareable": "Trova un __shareable_ver_link__ di questo interattivo insieme a decine di altri interattivi open-source per la scienza, la matematica e l\'ingegneria a __concord_org_link__."\n      },\n      "copyright": {\n        "copyright": "Diritto d\'autore",\n        "all_rights_reserved": "Tutti i diritti riservati.",\n        "license": "Il software è concesso in licenza sotto il __MIT_link__ license.",\n        "attribution": "Fornire l\'attribuzione al Consorzio Concord e all\'URL __concord_org_link__."\n      },\n      "md2d": {\n        "heatbath_icon_tooltip": "Bagno termico attivo",\n        "ke_icon_tooltip": "Gradiente di energia cinetica",\n        "invalid_object_position_alert": "Non puoi posizionare l\'oggetto in quel luogo.",\n        "aminoacid_menu": {\n          "hydrophobic": "Idrofobo",\n          "hydrophilic": "Idrofilo",\n          "glycine": "Glycine",\n          "alanine": "Alanina",\n          "valine": "Valina",\n          "leucine": "Leucina",\n          "isoleucine": "Isoleucine",\n          "phenylalanine": "Fenilalanina",\n          "proline": "Proline",\n          "tryptophan": "Triptofano",\n          "methionine": "Metionina",\n          "cysteine": "Cisteina",\n          "tyrosine": "Tirosina",\n          "asparagine": "Asparagina",\n          "glutamine": "Glutammina",\n          "serine": "Serina",\n          "threonine": "Treonina",\n          "asparticacid": "Acido aspartico",\n          "glutamicacid": "Acido glutammico",\n          "lysine": "Lisina",\n          "arginine": "Arginina",\n          "histidine": "Istidina"\n        },\n        "mutations_menu": {\n          "substitution_mutation": "Mutazione di sostituzione",\n          "insertion_mutation": "Mutazione di inserzione",\n          "deletion_mutation": "Mutazione deletione",\n          "insert": "Inserire"\n        }\n      },\n      "sensor": {\n        "select_sensor": "Selezionare Sensore",\n        "select_sensor_type": "Seleziona il tipo di sensore ...",\n        "reading": "Lettura:",\n        "zero": "Zero",\n        "zeroing": "Azzeramento...",\n        "loading_sensor": "Sensore di carico...",\n        "choose_sensor_title": "Selezionare un sensore:",\n        "connect": "Collegare",\n        "address_labquest2": "Indirizzo di LabQuest2",\n        "messages": {\n          "ready": "Pronta da raccogliere.",\n          "ready_nocontrol": "Si prega di fermare __controlling_client__ raccoglitore di dati per raccogliere i dati qui.",\n          "ready_nocontrol_noname": "Fermati l\'altro collezionista attivo per raccogliere i dati qui.",\n          "no_sensors": "Nessun sensore è stato trovato.",\n          "no_devices": "Nessun dispositivo collegato.",\n          "not_connected": "Non collegata.",\n          "connecting": "Connessione in corso ...",\n          "connection_in_progress": "Collegamento ai sensori. Se viene visualizzato un messaggio sensorconnector.concord.org, si prega di accettarlo.",\n          "connection_failed": "Connessione fallita. __retry_link__",\n          "connection_failed_retry_link_text": "Riprova",\n          "connection_failed_alert": "Il Concord Consortium Sensor Connector non è installato o non è in esecuzione. Per favore __click_here_link__ per istruzioni sull\'utilizzo del connettore del sensore.",\n          "connection_failed_labquest2_alert": "Impossibile connettersi a LabQuest2. Assicurarsi che l\'indirizzo sia corretto e che il LabQuest2 possa essere raggiunto da questo computer",\n          "tare_labquest2_alert": "Il LabQuest2 deve raccogliere i dati dal vivo per zero. Impostare una nuova esecuzione su LabQuest2 oppure fare clic sull\'icona del contatore in alto a sinistra.",\n          "click_here": "clicca qui",\n          "connected": "Collegato.",\n          "connected_start_labquest2": "Collegato. Avviare l\'inizio del tuo LabQuest2 per iniziare.",\n          "connected_start_sensorconnector": "Si prega di fermare __controlling_client__ raccoglitore di dati per raccogliere i dati qui.",\n          "connected_start_sensorconnector_noname": "Ferma l\'altro collezionista attivo per raccogliere i dati qui.",\n          "starting_data_collection": "Inizia la raccolta dati ...",\n          "error_starting_data_collection": "Errore durante la raccolta dati.",\n          "error_starting_data_collection_alert": "Impossibile avviare la raccolta dati. Assicurarsi che sia abilitato (avvio remoto)",\n          "collecting_data": "Raccolta dati.",\n          "collecting_data_stop_labquest2": "Raccolta dati. Arrestare la sosta sul tuo LabQuest2 per terminare.",\n          "collecting_data_stop_sensorconnector": "Raccolta dati.",\n          "no_data": "Nessun dato disponibile.",\n          "no_data_alert": "Il connettore del sensore non sembra riportare i dati per il dispositivo collegato",\n          "no_data_labquest2_alert": "Il LabQuest non sembra riportare i dati per il dispositivo collegato",\n          "canceling_data_collection": "Annullamento della raccolta dati ...",\n          "error_canceling_data_collection": "Errore durante l\'annullamento della raccolta dati.",\n          "error_canceling_data_collection_alert": "Impossibile annullare la raccolta dei dati. Assicurarsi che sia abilitato (avvio remoto)",\n          "stopping_data_collection": "Arresto della raccolta dati ...",\n          "error_stopping_data_collection": "Errore durante l\'arresto della raccolta dati.",\n          "error_stopping_data_collection_alert": "Impossibile arrestare la raccolta dati. Assicurarsi che sia abilitato (avvio remoto)",\n          "data_collection_stopped": "La raccolta dati è stata interrotta.",\n          "data_collection_complete": "La raccolta dei dati è completa.",\n          "disconnected": "Scollegato.",\n          "java_applet_error": "Sembra che gli applet Java non possano essere eseguiti nel tuo browser. Se è in grado di risolvere questo problema, ricaricare la pagina per utilizzare il sensore",\n          "java_applet_not_loading": "L\'applet del sensore sembra non essere caricato. Se è in grado di risolvere questo problema, ricaricare la pagina per utilizzare il sensore",\n          "unexpected_error": "Si è verificato un errore imprevisto quando si connette al sensore.",\n          "sensor_not_attached": "Il __sensor_name__ non sembra essere allegato. Provare a ricollegarlo, quindi fare clic su \\"$t(sensor.messages.try_again)\\".",\n          "sensor_or_device_unplugged": "Il __sensor_or_device_name__ È stato scollegato. Provare a collegarlo nuovamente e quindi fare clic su \\"$t(sensor.messages.try_again)\\".",\n          "try_again": "Riprova",\n          "cancel": "Annulla"\n        },\n        "measurements": {\n          "sensor_reading": "Lettura del sensore",\n          "time": "Tempo",\n          "distance": "Distanza",\n          "acceleration": "Accelerazione",\n          "altitude": "Altitudine",\n          "angle": "Angolo",\n          "CO2": "CO₂",\n          "CO2_concentration": "CO₂ Concentrazione",\n          "charge": "Carica",\n          "conductivity": "Conducibilità",\n          "current": "Current",\n          "dissolved_oxygen": "FARE",\n          "flow_rate": "Portata",\n          "fluorescence_405_nm": "Fluorescenza 405 nm",\n          "fluorescence_500_nm": "Fluorescenza 500 nm",\n          "force": "Vigore",\n          "intensity": "Intensità",\n          "light_level": "Livello luminoso",\n          "light_intensity": "Intensità luminosa",\n          "magnetic_field": "Campo magnetico",\n          "position": "Posizione",\n          "potential": "Potenziale",\n          "pressure": "Pressure",\n          "signal": "Segnale",\n          "sound_level": "Livello audio",\n          "speed": "Velocità",\n          "temperature": "Temperatura",\n          "transmittance": "Trasmissione",\n          "turbidity": "Torbidità",\n          "UV_intensity": "Intensità UV",\n          "velocity": "Velocità",\n          "volume": "Volume",\n          "pH": "pH",\n          "acidity": "Acidità",\n          "O2_concentration": "O₂ Concentrazione"\n        },\n        "names": {\n          "sensor": "sensore",\n          "no_sensor": "(Nessun sensore)",\n          "light": "Leggero",\n          "motion": "Movimento",\n          "accelerometer": "Accelerometro",\n          "dissolved_oxygen": "Ossigeno dissolto",\n          "pressure": "Pressione",\n          "charge_sensor": "Sensore di carica",\n          "voltage": "Voltaggio",\n          "pH": "pH",\n          "CO2_gas": "CO₂ Gas",\n          "colorimeter": "Colorimetro",\n          "conductivity": "Conducibilità",\n          "current": "Attuale",\n          "temperature": "Temperature",\n          "force": "Vigore",\n          "anemometer": "Anemometro",\n          "hand_dynamometer": "Dynamometer della mano",\n          "heart_rate": "Frequenza cardiaca",\n          "magnetic_field": "Campo magnetico",\n          "rotary_motion": "Moto rotativo",\n          "linear_position_sensor": "Sensore di posizione lineare",\n          "sound_level": "Livello audio",\n          "spectrophotometer": "spettrofotometro",\n          "spirometer": "Spirometro",\n          "turbidity": "torbidità",\n          "UV_sensor": "Sensore UV",\n          "drop_counter": "Contatore di caduta",\n          "altitude": "Altitudine",\n          "goMotion": "GoMotion",\n          "goTemp": "Sensore di temperatura GoIO",\n          "goLinkTemperature": "Sensore di temperatura GoIO",\n          "goLinkLight": "Sensore di luce GoIO",\n          "goLinkForce": "Sensore di forza GoIO",\n          "goLinkPH": "Sensore pH GoIO",\n          "goLinkCO2": "Sensore GoIO CO₂",\n          "goLinkO2": "Sensore GoIO O2",\n          "labQuestMotion": "LabQuest Motion Sensore",\n          "labQuestTemperature": "Sensore di temperatura LabQuest",\n          "labQuestLight": "Sensore di luce LabQuest",\n          "labQuestForce": "Sensore di forza LabQuest",\n          "labQuestPH": "Sensore pH LabQuest",\n          "labQuestCO2": "Sensore COQQuest CO₂",\n          "labQuestO2": "Sensore LabQuest O2"\n        }\n      }\n    }\n  },\n  \n  "pl": {\n    "translation": {\n      "banner": {\n        "about": "O symulacji",\n        "about_tooltip": "Instrukcje",\n        "share": "Udostępnij",\n        "share_tooltip": "Udostępnij tę symulację za pomocą e-mail, komunikatora lub umieść na swojej stronie internetowej",\n        "lang_tooltip": "Zmień język",\n        "reload_tooltip": "Załaduj symulację ponownie",\n        "help_tooltip": "Pokaż pomoc",\n        "credits_tooltip": "Dowiedz się więcej o Concord Consortium",\n        "fullscreen_tooltip": "Tryb pełnoekranowy",\n        "video_play_pause_tooltip": "Uruchom lub zatrzymaj symulację",\n        "video_reset_tooltip": "Zresetuj symulację",\n        "video_step_back_tooltip": "Krok wstecz",\n        "video_step_forward_tooltip": "Krok do przodu",\n        "text_start": "Uruchom",\n        "text_start_tooltip": "Uruchom symulację lub pobieranie danych",\n        "text_stop": "Zatrzymaj",\n        "text_stop_tooltip": "Zatrzymaj symulację lub pobieranie danych",\n        "text_reset": "Zresetuj",\n        "text_reset_tooltip": "Zresetuj symulację lub pobieranie danych"\n      },\n      "dialog": {\n        "close_tooltip": "Zamknij"\n      },\n      "about_dialog": {\n        "title": "O symulacji: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "Udostępnij: __interactive_title__",\n        "link": "odnośnik",\n        "paste_email_im": "Wklej ten __link__ do wiadomości e-mail lub wyślij za pomocą komunikatora.",\n        "paste_html": "Aby zamieścić symulację na swojej stronie lub blogu, wklej kod HTML znajdujący się poniżej.",\n        "select_size": "Wybierz rozmiar:",\n        "size_larger": "__val__% większy",\n        "size_actual": "aktualny",\n        "size_smaller": "__val__% mniejszy"\n      },\n      "credits_dialog": {\n        "title": "__interactive_title__",\n        "credits_text": "Ta symulacja została stworzona przez __CC_link__ przy użyciu __Next_Gen_MW_link__. Prace zostały sfinansowana przez grant otrzymany od __Google_link__.",\n        "shareable_ver": "Wersję tej symulacji, którą możesz udostępnić",\n        "find_shareable": "__shareable_ver_link__, wraz z setkami innych, darmowych symulacji i ćwiczeń znajdziesz na __concord_org_link__."\n      },\n      "copyright": {\n        "copyright": "Copyright",\n        "all_rights_reserved": "Wszystkie prawa zastrzeżone.",\n        "license": "To oprogramowanie jest rozpowszechniane na zasadach wolnej licencji __MIT_link__. Szczegóły licencji znajdują się w pliku __license_link__.",\n        "attribution": "Proszę umieścić odniesienia do Concord Consortium oraz witryny __concord_org_link__."\n      },\n      "md2d": {\n        "heatbath_icon_tooltip": "Stała temperatura",\n        "ke_icon_tooltip": "Skala energii kinetycznej",\n        "invalid_object_position_alert": "Niepoprawna pozycja obiektu.",\n        "aminoacid_menu": {\n          "hydrophobic": "Hydrofobowe",\n          "hydrophilic": "Hydrofilowe",\n          "glycine": "Glicyna",\n          "alanine": "Alanina",\n          "valine": "Walina",\n          "leucine": "Leucyna",\n          "isoleucine": "Izoleucyna",\n          "phenylalanine": "Fenyloalanina",\n          "proline": "Prolina",\n          "tryptophan": "Tryptofan",\n          "methionine": "Metionina",\n          "cysteine": "Cysteina",\n          "tyrosine": "Tyrozyna",\n          "asparagine": "Asparagina",\n          "glutamine": "Glutamina",\n          "serine": "Seryna",\n          "threonine": "Treonina",\n          "asparticacid": "Kwas asparaginowy",\n          "glutamicacid": "Kwas glutaminowy",\n          "lysine": "Lizyna",\n          "arginine": "Arginina",\n          "histidine": "Histydyna"\n        },\n        "mutations_menu": {\n          "substitution_mutation": "Substytucja",\n          "insertion_mutation": "Insercja",\n          "deletion_mutation": "Delecja",\n          "insert": "Wstaw"\n        }\n      },\n      "sensor": {\n        "select_sensor": "Wybierz sensor",\n        "select_sensor_type": "Wybierz rodzaj sensora...",\n        "reading": "Odczyt:",\n        "zero": "Wyzeruj",\n        "zeroing": "Zerowanie...",\n        "loading_sensor": "Ładowanie sensora...",\n        "choose_sensor_title": "Wybierz sensor:",\n        "connect": "Połącz",\n        "address_labquest2": "adres serwera LabQuest2",\n        "messages": {\n          "ready": "Gotowy do odczytu.",\n          "no_sensors": "Nie znaleziono sensorów.",\n          "no_devices": "Żadne urządzenie nie jest podłączone.",\n          "not_connected": "Brak połączenia.",\n          "connecting": "Łączenie...",\n          "connection_failed": "Połączenie nieudane.",\n          "connection_failed_alert": "Concord Consortium Sensor Connector nie jest zainstalowany lub nie działa. Proszę __click_here_link__ żeby otworzyć instrukcję dotyczącą korzystania z Sensor Connector.",\n          "connection_failed_labquest2_alert": "Nie udało się połączyć z LabQuest2. Upewnij się, że adres jest poprawny i LabQuest2 jest osiągalny z tego komputera.",\n          "tare_labquest2_alert": "LabQuest2 musi pobierać rzeczywiste dane aby się wyzerować. Ustaw nowy eksperyment na LabQuest2 lub kliknij ikonę licznika na górze po lewej stronie.",\n          "click_here": "kliknąć tutaj",\n          "connected": "Połączono.",\n          "connected_start_labquest2": "Połączono. Naciśnij start na LabQuest2 aby rozpocząć.",\n          "starting_data_collection": "Rozpoczynanie pobierania danych...",\n          "error_starting_data_collection": "Błąd podczas rozpoczynania pobierania danych.",\n          "error_starting_data_collection_alert": "Nie udało się rozpocząć pobierania danych. Upewnij się, że (remote starting) jest włączony",\n          "collecting_data": "Pobieranie danych.",\n          "collecting_data_stop_labquest2": "Pobieranie danych. Naciśnij stop na LabQuest2 aby zakończyć.",\n          "no_data": "Brak dostępnych danych.",\n          "no_data_alert": "Sensor Connector nie wydaje się raportować żadnych danych dla podłączonego urządzenia",\n          "no_data_labquest2_alert": "LabQuest nie wydaje się raportować żadnych danych dla podłączonego urządzenia",\n          "canceling_data_collection": "Anulowanie pobierania danych...",\n          "error_canceling_data_collection": "Błąd podczas anulowania pobierania danych.",\n          "error_canceling_data_collection_alert": "Nie udało się anulować pobierania danych. Upewnij się, że (remote starting) jest włączony",\n          "stopping_data_collection": "Zatrzymywanie pobierania danych...",\n          "error_stopping_data_collection": "Błąd podczas zatrzymywania pobierania danych.",\n          "error_stopping_data_collection_alert": "Nie udało się zatrzymać pobierania danych. Upewnij się, że (remote starting) jest włączony",\n          "data_collection_stopped": "Pobieranie danych zatrzymane.",\n          "data_collection_complete": "Pobieranie danych zakończone.",\n          "disconnected": "Rozłączono.",\n          "java_applet_error": "Aplety Java prawodpodobnie nie mogą być uruchomione w twojej przeglądarce. Jeżeli jesteś w stanie to naprawić to potem odśwież stronę, żeby użyć sensora.",\n          "java_applet_not_loading": "Sensor Aplet nie ładuje się. Jeżeli jesteś w stanie to naprawić to potem odśwież stronę, żeby użyć sensora.",\n          "unexpected_error": "Wystąpił nieoczekiwany błąd podczas próby połączenia z sensorem.",\n          "sensor_not_attached": "__sensor_name__ wydaje się być niedpołączony. Spróbuj podłączyć go ponownie i potem kliknij \\"$t(sensor.messages.try_again)\\".",\n          "sensor_or_device_unplugged": "__sensor_or_device_name__ został odłączony. Spróbuj podłączyć go ponownie i potem kliknij \\"$t(sensor.messages.try_again)\\".",\n          "try_again": "Spróbuj ponownie",\n          "cancel": "Anuluj"\n        },\n        "measurements": {\n          "sensor_reading": "Odczyt sensora",\n          "time": "Czas",\n          "distance": "Odległość",\n          "acceleration": "Przyspieszenie",\n          "altitude": "Wysokość",\n          "angle": "Kąt",\n          "CO2": "CO₂",\n          "CO2_concentration": "Stężenie CO₂",\n          "charge": "Ładunek",\n          "conductivity": "Przewodność właściwa",\n          "current": "Natężenie prądu",\n          "dissolved_oxygen": "Stężenie tlenu",\n          "flow_rate": "Strumień objętości",\n          "fluorescence_405_nm": "Fluorescencja 405 nm",\n          "fluorescence_500_nm": "Fluorescencja 500 nm",\n          "force": "Siła",\n          "intensity": "Natężenie",\n          "light_level": "Natężenie światła",\n          "light_intensity": "Intensywność światła",\n          "magnetic_field": "Pole magnetyczne",\n          "position": "Pozycja",\n          "potential": "Potencjał",\n          "pressure": "Ciśnienie",\n          "signal": "Sygnał",\n          "sound_level": "Natężenie dźwięku",\n          "speed": "Szybkość",\n          "temperature": "Temperatura",\n          "transmittance": "Transmitacja",\n          "turbidity": "Mętność",\n          "UV_intensity": "Natężenie UV",\n          "velocity": "Prędkość",\n          "volume": "Objętość",\n          "pH": "pH",\n          "acidity": "Kwasowość",\n          "O2_concentration": "Stężenie O₂"\n        },\n        "names": {\n          "sensor": "czujnik",\n          "no_sensor": "(brak czujnika)",\n          "light": "Światło",\n          "motion": "Ruch",\n          "accelerometer": "Akcelerometr",\n          "dissolved_oxygen": "Stężenie tlenu",\n          "pressure": "Ciśnienie",\n          "charge_sensor": "Czujnik ładunku",\n          "voltage": "Napięcie",\n          "pH": "pH",\n          "CO2_gas": "Czujnik CO₂",\n          "colorimeter": "Kolorymetr",\n          "conductivity": "Przewodność właściwa",\n          "current": "Natężenie prądu",\n          "temperature": "Temperatura",\n          "force": "Siła",\n          "anemometer": "Anemometr",\n          "hand_dynamometer": "Dynamometr",\n          "heart_rate": "Pulsometr",\n          "magnetic_field": "Pole magnetyczne",\n          "rotary_motion": "Ruch obrotowy",\n          "linear_position_sensor": "Czujnik położenia",\n          "sound_level": "Natężenie dźwięku",\n          "spectrophotometer": "Spektrofotometr",\n          "spirometer": "Spirometr",\n          "turbidity": "Mętność",\n          "UV_sensor": "Czujnik UV",\n          "drop_counter": "Licznik kropli",\n          "altitude": "Wysokość",\n          "goMotion": "GoMotion",\n          "goTemp": "GoIO czujnik temperatury",\n          "goLinkTemperature": "GoIO czujnik temperatury",\n          "goLinkLight": "GoIO czujnik światła",\n          "goLinkForce": "GoIO czujnik siły",\n          "goLinkPH": "GoIO czujnik pH",\n          "goLinkCO2": "GoIO czujnik CO₂",\n          "goLinkO2": "GoIO czujnik O₂",\n          "labQuestMotion": "LabQuest czujnik ruchu",\n          "labQuestTemperature": "LabQuest czujnik temperatury",\n          "labQuestLight": "LabQuest czujnik światła",\n          "labQuestForce": "LabQuest czujnik siły",\n          "labQuestPH": "LabQuest czujnik pH",\n          "labQuestCO2": "LabQuest czujnik CO₂",\n          "labQuestO2": "LabQuest czujnik O₂"\n        }\n      }\n    }\n  },\n  \n  "nb-NO": {\n    "translation": {\n      "banner": {\n        "about": "Om simuleringen",\n        "about_tooltip": "Bruksanvisning",\n        "share": "Del",\n        "share_tooltip": "Del med e-post, melding eller innbygging",\n        "lang_tooltip": "Velg språk",\n        "reload_tooltip": "Last simuleringen på nytt",\n        "help_tooltip": "Vis hjelpetekster",\n        "credits_tooltip": "Lær mer om Concord Consortium",\n        "fullscreen_tooltip": "Slå fullskjermvisning av/på",\n        "video_play_pause_tooltip": "Start og stopp simuleringen",\n        "video_reset_tooltip": "Tilbakestill simuleringen",\n        "video_step_back_tooltip": "Gå et steg bakover",\n        "video_step_forward_tooltip": "Gå et steg framover",\n        "text_start": "Start",\n        "text_start_tooltip": "Start simuleringen eller datainnsamlingen",\n        "text_stop": "Stopp",\n        "text_stop_tooltip": "Stopp simuleringen eller datainnsamlingen",\n        "text_reset": "Tilbakestill",\n        "text_reset_tooltip": "Tilbakestill simuleringen eller datainnsamlingen"\n      },\n      "dialog": {\n        "close_tooltip": "Lukk"\n      },\n      "about_dialog": {\n        "title": "Om: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "-Del: __interactive_title__",\n        "link": "lenke",\n        "paste_email_im": "Lim inn denne __link__ i en e-post eller melding.",\n        "paste_html": "Lim inn HTML for innbygging på et nettsted eller en blogg.",\n        "select_size": "Velg størrelse:",\n        "size_larger": "__val__% større",\n        "size_actual": "faktisk",\n        "size_smaller": "__val__% mindre"\n      },\n      "credits_dialog": {\n        "title": "Informasjon om: __interactive_title__",\n        "credits_text": "Denne simuleringen er laget av __CC_link__ med bruk av __Next_Gen_MW_link__ programvare, finanisert av en bevilgning fra __Google_link__.",\n        "shareable_ver": "simuleringen kan deles",\n        "find_shareable": "Du finner en __shareable_ver_link__ av denne simuleringen, sammen med dusinvis andre open-source simuleringer for vitenskap, matematikk og teknologi på __concord_org_link__."\n      },\n      "copyright": {\n        "copyright": "Copyright",\n        "all_rights_reserved": "Alle rettigheter reservert.",\n        "license": "Denne programvaren er lisensiert under __MIT_link__ lisens.",\n        "attribution": "Vær vennlig å referere til Concord Consortium og URLen __concord_org_link__."\n      },\n      "md2d": {\n        "heatbath_icon_tooltip": "Varmebad er på",\n        "ke_icon_tooltip": "Kinetisk energigradient",\n        "invalid_object_position_alert": "You can\'t drop the object there.",\n        "aminoacid_menu": {\n          "hydrophobic": "Hydrofob",\n          "hydrophilic": "Hydrofil",\n          "glycine": "Glysin",\n          "alanine": "Alanin",\n          "valine": "Valin",\n          "leucine": "Leucin",\n          "isoleucine": "Isoleucin",\n          "phenylalanine": "Fenylalanin",\n          "proline": "Prolin",\n          "tryptophan": "Tryptofan",\n          "methionine": "Metionin",\n          "cysteine": "Cystin",\n          "tyrosine": "Tyrosin",\n          "asparagine": "Aspargin",\n          "glutamine": "Glutamin",\n          "serine": "Serin",\n          "threonine": "Threonin",\n          "asparticacid": "Asparginsyre",\n          "glutamicacid": "Glutaminsyre",\n          "lysine": "Lysin",\n          "arginine": "Arginin",\n          "histidine": "Histidin"\n        },\n        "mutations_menu": {\n          "substitution_mutation": "Substitusjonsmutasjon",\n          "insertion_mutation": "Insersjonsmutasjon",\n          "deletion_mutation": "Delesjonsmutasjon",\n          "insert": "Sett inn"\n        }\n      },\n      "sensor": {\n        "select_sensor": "Velg sensor",\n        "select_sensor_type": "Velg sensortype...",\n        "reading": "Avlesning:",\n        "zero": "Null",\n        "zeroing": "Nullstilling...",\n        "loading_sensor": "Laster sensor...",\n        "choose_sensor_title": "Velg en sensor:",\n        "connect": "Koble til",\n        "address_labquest2": "adressen til LabQuest2",\n        "messages": {\n          "ready": "Klar til innsamling.",\n          "no_sensors": "Finner ingen sensorer.",\n          "no_devices": "Ingen enheter er tilkoblet.",\n          "not_connected": "Ikke tilkoblet.",\n          "connecting": "Kobler til...",\n          "connection_failed": "Tilkobling feilet.",\n          "connection_failed_alert": "Concord Consortium Sensor Connector er ikke installert eller kjører ikke. Vennligst __click_here_link__ for instruksjoner om hvordan du bruker Sensor Connector.",\n          "connection_failed_labquest2_alert": "Kunne ikke koble til LabQuest2. Vennligst sjekk at adressen er riktig, og at LabQuest2 kan nås fra denne datamaskinen",\n          "tare_labquest2_alert": "LabQuest2 må samle inn data i sanntid for å kunne nullstilles. Sett enten opp en ny måling, eller klikk målerikonet oppe til venstre.",\n          "click_here": "klikk her",\n          "connected": "Tilkoblet.",\n          "connected_start_labquest2": "Tilkoblet. Trykk start på LabQuest2 for å begynne.",\n          "starting_data_collection": "Starter datainnsamling...",\n          "error_starting_data_collection": "Feil ved start av datainnsamling.",\n          "error_starting_data_collection_alert": "Kunne ikke starte datainnsamling. Forsikre deg om at (fjernstart) er aktivert.",\n          "collecting_data": "Samler inn data.",\n          "collecting_data_stop_labquest2": "Samler inn data. Trykk stopp på LabQuest2 for å avslutte.",\n          "no_data": "Ingen data er tilgengelig.",\n          "no_data_alert": "Sensorkonnektoren ser ikke ut til å levere data fra den tilkoblede enheten",\n          "no_data_labquest2_alert": "LabQuest ser ikke ut til å rapportere data fra den tilkoblede enheten",\n          "canceling_data_collection": "Avbryter datainnsamling...",\n          "error_canceling_data_collection": "Feil ved avbrytelse av datainnsamling.",\n          "error_canceling_data_collection_alert": "Kunne ikke avbryte datainnsamling. Sjekk at (fjernstart) er aktivert",\n          "stopping_data_collection": "Stopper datainnsamling...",\n          "error_stopping_data_collection": "Feil ved stopp av datainnsamling.",\n          "error_stopping_data_collection_alert": "Kunne ikke stoppe datainnsamling. Sjekk at (fjernstart) er aktivert",\n          "data_collection_stopped": "Datainnsamling stoppet.",\n          "data_collection_complete": "Datainnsamling er fullført.",\n          "disconnected": "Koblet fra.",\n          "java_applet_error": "Det ser ut som javaappleter ikke kan kjøre i nettleseren. Hvis du kan rette på dette, last siden på nytt for å bruke sensoren",\n          "java_applet_not_loading": "Sensorappleten ser ikke ut til å virke. Hvis du kan rette på dette, kan du laste siden på nytt for å bruke sensoren",\n          "unexpected_error": "Det oppsto en uventet feil da sensoren ble tilkoblet.",\n          "sensor_not_attached": " __sensor_name__ ser ikke ut til å være tilkoblet. Prøv å koble til på nytt, og klikk på \\"$t(sensor.messages.try_again)\\".",\n          "sensor_or_device_unplugged": " __sensor_or_device_name__ ble frakoblet. Prøv å koble til på nytt, og klikk på \\"$t(sensor.messages.try_again)\\".",\n          "try_again": "Prøv igjen",\n          "cancel": "Avbryt"\n        },\n        "measurements": {\n          "sensor_reading": "Sensoravlesning",\n          "time": "Tid",\n          "distance": "Avstand",\n          "acceleration": "Akselerasjon",\n          "altitude": "Høyde",\n          "angle": "Vinkel",\n          "CO2": "CO₂",\n          "CO2_concentration": "CO₂-konsentrasjon",\n          "charge": "Ladning",\n          "conductivity": "Ledningsevne",\n          "current": "Strøm",\n          "dissolved_oxygen": "OO",\n          "flow_rate": "Gjennomstrømningsmengde",\n          "fluorescence_405_nm": "Fluorescens 405 nm",\n          "fluorescence_500_nm": "Fluorescens 500 nm",\n          "force": "Kraft",\n          "intensity": "Intensitet",\n          "light_level": "Lysnivå",\n          "light_intensity": "Lysintensitet",\n          "magnetic_field": "Magnetfelt",\n          "position": "Posisjon",\n          "potential": "Potensial",\n          "pressure": "Trykk",\n          "signal": "Signal",\n          "sound_level": "Lydnivå",\n          "speed": "Fart",\n          "temperature": "Temperatur",\n          "transmittance": "Transmittans",\n          "turbidity": "Turbiditet",\n          "UV_intensity": "UV-intensitet",\n          "velocity": "Hastighet",\n          "volume": "Volum",\n          "pH": "pH",\n          "acidity": "Surhet",\n          "O2_concentration": "O₂-konsentrasjon"\n        },\n        "names": {\n          "sensor": "sensor",\n          "no_sensor": "(ingen sensor)",\n          "light": "Lys",\n          "motion": "Bevegelse",\n          "accelerometer": "Akselerometer",\n          "dissolved_oxygen": "Oppløst oksygen",\n          "pressure": "Trykk",\n          "charge_sensor": "Ladningssensor",\n          "voltage": "Spenning",\n          "pH": "pH",\n          "CO2_gas": "CO₂-gass",\n          "colorimeter": "Kolorimeter",\n          "conductivity": "Ledningsevne",\n          "current": "Strøm",\n          "temperature": "Temperatur",\n          "force": "Kraft",\n          "anemometer": "Anemometer",\n          "hand_dynamometer": "Hånddynamometer",\n          "heart_rate": "Hjertefrekvens",\n          "magnetic_field": "Magnetfelt",\n          "rotary_motion": "Rotasjon",\n          "linear_position_sensor": "Sensor for lineær posisjon",\n          "sound_level": "Lydnivå",\n          "spectrophotometer": "Spektrofotometer",\n          "spirometer": "Spirometer",\n          "turbidity": "Turbiditet",\n          "UV_sensor": "UV-sensor",\n          "drop_counter": "Dråpeteller",\n          "altitude": "Høyde",\n          "goMotion": "GoMotion",\n          "goTemp": "GoIO temperatursensor",\n          "goLinkTemperature": "GoIO temperatursensor",\n          "goLinkLight": "GoIO lyssensor",\n          "goLinkForce": "GoIO kraftsensor",\n          "goLinkPH": "GoIO pH-sensor",\n          "goLinkCO2": "GoIO CO₂-sensor",\n          "goLinkO2": "GoIO CO₂-sensor",\n          "labQuestMotion": "LabQuest bevegelsessensor",\n          "labQuestTemperature": "LabQuest temperatursensor",\n          "labQuestLight": "LabQuest lyssensor",\n          "labQuestForce": "LabQuest kraftsensor",\n          "labQuestPH": "LabQuest pH-sensor",\n          "labQuestCO2": "LabQuest CO₂-sensor",\n          "labQuestO2": "LabQuest O₂-sensor"\n        }\n      }\n    }\n  },\n  \n  "nn-NO": {\n    "translation": {\n      "banner": {\n        "about": "Om simuleringa",\n        "about_tooltip": "Brukarrettleiing",\n        "share": "Del",\n        "share_tooltip": "Del med e-post, melding eller innbygging",\n        "lang_tooltip": "Vel språk",\n        "reload_tooltip": "Last simuleringa på nytt",\n        "help_tooltip": "Vis hjelpetekstar",\n        "credits_tooltip": "Lær meir om Concord Consortium",\n        "fullscreen_tooltip": "Slå fullskjermvising av/på",\n        "video_play_pause_tooltip": "Start og stopp simuleringa",\n        "video_reset_tooltip": "Tilbakestill simuleringa",\n        "video_step_back_tooltip": "Gå eit steg bakover",\n        "video_step_forward_tooltip": "Gå eit steg framover",\n        "text_start": "Start",\n        "text_start_tooltip": "Start simuleringa eller datainnsamlinga",\n        "text_stop": "Stopp",\n        "text_stop_tooltip": "Stopp simuleringa eller datainnsamlinga",\n        "text_reset": "Tilbakestill",\n        "text_reset_tooltip": "Tilbakestill simuleringa eller datainnsamlinga"\n      },\n      "dialog": {\n        "close_tooltip": "Lukk"\n      },\n      "about_dialog": {\n        "title": "Om: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "-Del: __interactive_title__",\n        "link": "lenke",\n        "paste_email_im": "Lim inn denne __link__ i ein e-post eller melding.",\n        "paste_html": "Lim inn HTML for innbygging på ein nettstad eller ein blogg.",\n        "select_size": "Vel storleik:",\n        "size_larger": "__val__% større",\n        "size_actual": "faktisk",\n        "size_smaller": "__val__% mindre"\n      },\n      "credits_dialog": {\n        "title": "Informasjon om: __interactive_title__",\n        "credits_text": "Denne simuleringa er laga av __CC_link__ med bruk av __Next_Gen_MW_link__ programvare, finanisert av ei løyving frå __Google_link__.",\n        "shareable_ver": "simuleringa kan delast",\n        "find_shareable": "Du finn ein __shareable_ver_link__ av denne simuleringa, saman med dusinvis andre open-source simuleringar for vitskap, matematikk og teknologi på __concord_org_link__."\n      },\n      "copyright": {\n        "copyright": "Copyright",\n        "all_rights_reserved": "Alle rettar reservert.",\n        "license": "Denne programvara er lisensiert under __MIT_link__ eller lisens.",\n        "attribution": "Vær venleg å referere til Concord Consortium og URLen __concord_org_link__."\n      },\n      "md2d": {\n        "heatbath_icon_tooltip": "Varmebad er på",\n        "ke_icon_tooltip": "Kinetisk energigradient",\n        "invalid_object_position_alert": "You can\'t drop the object there.",\n        "aminoacid_menu": {\n          "hydrophobic": "Hydrofob",\n          "hydrophilic": "Hydrofil",\n          "glycine": "Glysin",\n          "alanine": "Alanin",\n          "valine": "Valin",\n          "leucine": "Leucin",\n          "isoleucine": "Isoleucin",\n          "phenylalanine": "Fenylalanin",\n          "proline": "Prolin",\n          "tryptophan": "Tryptofan",\n          "methionine": "Metionin",\n          "cysteine": "Cystin",\n          "tyrosine": "Tyrosin",\n          "asparagine": "Aspargin",\n          "glutamine": "Glutamin",\n          "serine": "Serin",\n          "threonine": "Threonin",\n          "asparticacid": "Asparginsyre",\n          "glutamicacid": "Glutaminsyre",\n          "lysine": "Lysin",\n          "arginine": "Arginin",\n          "histidine": "Histidin"\n        },\n        "mutations_menu": {\n          "substitution_mutation": "Substitusjonsmutasjon",\n          "insertion_mutation": "Insersjonsmutasjon",\n          "deletion_mutation": "Delesjonsmutasjon",\n          "insert": "Sett inn"\n        }\n      },\n      "sensor": {\n        "select_sensor": "Vel sensor",\n        "select_sensor_type": "Vel sensortype...",\n        "reading": "Avlesing:",\n        "zero": "Null",\n        "zeroing": "Nullstilling...",\n        "loading_sensor": "Lastar sensor...",\n        "choose_sensor_title": "Vel en sensor:",\n        "connect": "Kople til",\n        "address_labquest2": "adressen til LabQuest2",\n        "messages": {\n          "ready": "Klar til innsamling.",\n          "no_sensors": "Finn ingen sensorer.",\n          "no_devices": "Inga eining er kopla til.",\n          "not_connected": "Ikkje tilkopla.",\n          "connecting": "Kopla til...",\n          "connection_failed": "Tilkopling feila.",\n          "connection_failed_alert": "Concord Consortium Sensor Connector er ikke installert eller kjører ikke. Vær venleg __click_here_link__ for, om naudsynt, å laste ned eit installasjonsprogram for sensortilkoplinga.",\n          "connection_failed_labquest2_alert": "Kunne ikkje kople til LabQuest2. Vær venleg, sjekk at adressen er rett, og at LabQuest2 kan nåast frå denne datamaskina",\n          "tare_labquest2_alert": "LabQuest2 må samle inn data i sann tid for å kunne nullstillast. Sett anten opp en ny måling, eller klikk målårikonet oppe til venstre.",\n          "click_here": "klikk her",\n          "connected": "Tilkopla.",\n          "connected_start_labquest2": "Tilkopla. Trykk start på LabQuest2 for å byrje.",\n          "starting_data_collection": "Startar datainnsamling...",\n          "error_starting_data_collection": "Feil ved start av datainnsamling.",\n          "error_starting_data_collection_alert": "Kunne ikkje starte datainnsamling. Forsikre deg om at (fjernstart) er aktivert.",\n          "collecting_data": "Samlar inn data.",\n          "collecting_data_stop_labquest2": "Samlar inn data. Trykk stopp på LabQuest2 for å avslutte.",\n          "no_data": "Ingen data er tilgengelege.",\n          "no_data_alert": "Sensorkonnektoren ser ikkje ut til å levere data frå den tilkopla eininga",\n          "no_data_labquest2_alert": "LabQuest ser ikkje ut til å rapportere data frå den tilkopla eininga",\n          "canceling_data_collection": "Avbryt datainnsamlinga...",\n          "error_canceling_data_collection": "Feil på avbrytinga av datainnsamlinga.",\n          "error_canceling_data_collection_alert": "Kunne ikkje avbryte datainnsamlinga. Sjekk at (fjernstart) er aktivert",\n          "stopping_data_collection": "Stopper datainnsamlinga...",\n          "error_stopping_data_collection": "Feil ved stopp av datainnsamlinga.",\n          "error_stopping_data_collection_alert": "Kunne ikkje stoppe datainnsamlinga. Sjekk at (fjernstart) er aktivert",\n          "data_collection_stopped": "Datainnsamlinga stoppa.",\n          "data_collection_complete": "Datainnsamlinga er fullført.",\n          "disconnected": "Kopla frå.",\n          "java_applet_error": "Det ser ut som javaappleter ikkje kan køyre i nettlesaren. Hvis du kan rette på dette, last inn sida på nytt for å bruke sensoren",\n          "java_applet_not_loading": "Sensorappleten ser ikkje ut til å virke. Hvis du kan rette på dette, kan du laste inn sida på nytt for å bruke sensoren",\n          "unexpected_error": "Det oppsto ein uventa feil då sensoren vart tilkopla.",\n          "sensor_not_attached": " __sensor_name__ ser ikkje ut til å vere tilkopla. Prøv å kople til på nytt, og klikk på \\"$t(sensor.messages.try_again)\\".",\n          "sensor_or_device_unplugged": " __sensor_or_device_name__ ble frakopla. Prøv å kople til på nytt, og klikk på \\"$t(sensor.messages.try_again)\\".",\n          "try_again": "Prøv igjen",\n          "cancel": "Avbryt"\n        },\n        "measurements": {\n          "sensor_reading": "Sensoravlesing",\n          "time": "Tid",\n          "distance": "Avstand",\n          "acceleration": "Akselerasjon",\n          "altitude": "Høgd",\n          "angle": "Vinkel",\n          "CO2": "CO₂",\n          "CO2_concentration": "CO₂-konsentrasjon",\n          "charge": "Ladning",\n          "conductivity": "Leiingsevne",\n          "current": "Strøm",\n          "dissolved_oxygen": "OO",\n          "flow_rate": "Gjennomstrømmingsmengd",\n          "fluorescence_405_nm": "Fluorescens 405 nm",\n          "fluorescence_500_nm": "Fluorescens 500 nm",\n          "force": "Kraft",\n          "intensity": "Intensitet",\n          "light_level": "Lysnivå",\n          "light_intensity": "Lysintensitet",\n          "magnetic_field": "Magnetfelt",\n          "position": "Posisjon",\n          "potential": "Potensial",\n          "pressure": "Trykk",\n          "signal": "Signal",\n          "sound_level": "Lydnivå",\n          "speed": "Fart",\n          "temperature": "Temperatur",\n          "transmittance": "Transmittans",\n          "turbidity": "Turbiditet",\n          "UV_intensity": "UV-intensitet",\n          "velocity": "Hastigheit",\n          "volume": "Volum",\n          "pH": "pH",\n          "acidity": "Surleik",\n          "O2_concentration": "O₂-konsentrasjon"\n        },\n        "names": {\n          "sensor": "sensor",\n          "no_sensor": "(ingen sensor)",\n          "light": "Lys",\n          "motion": "Rørsle",\n          "accelerometer": "Akselerometer",\n          "dissolved_oxygen": "Løyst oksygen",\n          "pressure": "Trykk",\n          "charge_sensor": "Ladingssensor",\n          "voltage": "Spenning",\n          "pH": "pH",\n          "CO2_gas": "CO₂-gass",\n          "colorimeter": "Kolorimeter",\n          "conductivity": "Leiingsevne",\n          "current": "Strøm",\n          "temperature": "Temperatur",\n          "force": "Kraft",\n          "anemometer": "Anemometer",\n          "hand_dynamometer": "Handdynamometer",\n          "heart_rate": "Hjertefrekvens",\n          "magnetic_field": "Magnetfelt",\n          "rotary_motion": "Rotasjon",\n          "linear_position_sensor": "Sensor for lineær posisjon",\n          "sound_level": "Lydnivå",\n          "spectrophotometer": "Spektrofotometer",\n          "spirometer": "Spirometer",\n          "turbidity": "Turbiditet",\n          "UV_sensor": "UV-sensor",\n          "drop_counter": "Dropeteljar",\n          "altitude": "Høgd",\n          "goMotion": "GoMotion",\n          "goTemp": "GoIO temperatursensor",\n          "goLinkTemperature": "GoIO temperatursensor",\n          "goLinkLight": "GoIO lyssensor",\n          "goLinkForce": "GoIO kraftsensor",\n          "goLinkPH": "GoIO pH-sensor",\n          "goLinkCO2": "GoIO CO₂-sensor",\n          "goLinkO2": "GoIO CO₂-sensor",\n          "labQuestMotion": "LabQuest rørslesensor",\n          "labQuestTemperature": "LabQuest temperatursensor",\n          "labQuestLight": "LabQuest lyssensor",\n          "labQuestForce": "LabQuest kraftsensor",\n          "labQuestPH": "LabQuest pH-sensor",\n          "labQuestCO2": "LabQuest CO₂-sensor",\n          "labQuestO2": "LabQuest O₂-sensor"\n        }\n      }\n    }\n  },\n  \n  "pt-BR": {\n    "translation": {\n      "banner": {\n        "about": "Sobre",\n        "about_tooltip": "Instruções",\n        "share": "Compartilhar",\n        "share_tooltip": "Compartilha por e-mail, mensagem ou inclui em site",\n        "lang_tooltip": "Seleciona a língua",\n        "reload_tooltip": "Recarrega o interativo",\n        "help_tooltip": "Mostra dicas",\n        "credits_tooltip": "Saber mais sobre The Concord Consortium",\n        "fullscreen_tooltip": "Alterna para tela cheia",\n        "video_play_pause_tooltip": "Inicia / pausa a simulação",\n        "video_reset_tooltip": "Reinicia a simulação",\n        "video_step_back_tooltip": "Passo atrás",\n        "video_step_forward_tooltip": "Passo adiante",\n        "text_start": "Iniciar",\n        "text_start_tooltip": "Inicia a simulação ou coleta de dados",\n        "text_stop": "Parar",\n        "text_stop_tooltip": "Para a simulação ou coleta de dados",\n        "text_reset": "Reiniciar",\n        "text_reset_tooltip": "Reinicia a simulação ou coleta de dados",\n        "text_new_run": "Nova corrida",\n        "text_new_run_tooltip": "Prepara nova corrida experimental",\n        "text_analyze_data": "Analisar dados",\n        "text_analyze_data_tooltip": "Envia dados experimentais para CODAP"\n      },\n      "dialog": {\n        "close_tooltip": "Fecha"\n      },\n      "about_dialog": {\n        "title": "Sobre: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "Compartilhar: __interactive_title__",\n        "link": "link",\n        "paste_email_im": "Cole este __link__ no email ou mensagem.",\n        "paste_html": "Cole o HTML para incluir em site ou blog.",\n        "select_size": "Selecione o tamanho:",\n        "size_larger": "__val__% maior",\n        "size_actual": "real",\n        "size_smaller": "__val__% menor"\n      },\n      "credits_dialog": {\n        "title": "Créditos: __interactive_title__",\n        "credits_text": "Este interativo foi criado pelo __CC_link__ usando nosso __Next_Gen_MW_link__ software, com o apoio financeiro do __Google_link__.",\n        "shareable_ver": "versão compartilhável",\n        "find_shareable": "Encontre uma __shareable_ver_link__ deste interativo e mais dezenas de outros interativos de código aberto para ciência, matemática e engenharia em __concord_org_link__."\n      },\n      "copyright": {\n        "copyright": "Copyright",\n        "all_rights_reserved": "Todos direitos reservados.",\n        "license": "O software é licenciado sob a licença __MIT_link__.",\n        "attribution": "Por favor, forneça a atribuição ao Concord Consortium and a URL __concord_org_link__."\n      },\n      "md2d": {\n        "heatbath_icon_tooltip": "Banho de calor ativo",\n        "ke_icon_tooltip": "Gradiente de energia cinética",\n        "invalid_object_position_alert": "You can\'t drop the object there.",\n        "aminoacid_menu": {\n          "hydrophobic": "Hidrofóbico",\n          "hydrophilic": "Hidrofílico",\n          "glycine": "Glicina",\n          "alanine": "Alanina",\n          "valine": "Valina",\n          "leucine": "Leucina",\n          "isoleucine": "Isoleucina",\n          "phenylalanine": "Phenilalanina",\n          "proline": "Prolina",\n          "tryptophan": "Triptofano",\n          "methionine": "Metionina",\n          "cysteine": "Cisteína",\n          "tyrosine": "Tirosina",\n          "asparagine": "Asparagina",\n          "glutamine": "Glutamina",\n          "serine": "Serina",\n          "threonine": "Treonina",\n          "asparticacid": "Ácido aspártico",\n          "glutamicacid": "Ácido glutâmico",\n          "lysine": "Lisina",\n          "arginine": "Arginina",\n          "histidine": "Histidina"\n        },\n        "mutations_menu": {\n          "substitution_mutation": "Mutação de substituição",\n          "insertion_mutation": "Mutação de inserção",\n          "deletion_mutation": "Mutação de deleção",\n          "insert": "Inserir"\n        }\n      },\n      "sensor": {\n        "select_sensor": "Selecione o sensor",\n        "select_sensor_type": "Selecione o tipo de sensor...",\n        "reading": "Leitura:",\n        "zero": "Zero",\n        "zeroing": "Zerando...",\n        "loading_sensor": "Carregando sensor...",\n        "choose_sensor_title": "Selecione o sensor:",\n        "connect": "Conectar",\n        "address_labquest2": "endereço do LabQuest2",\n        "messages": {\n          "ready": "Pronto para coletar.",\n          "ready_nocontrol": "Por favor pare o coletor de dados __controlling_client__ para coletar dados aqui.",\n          "ready_nocontrol_noname": "Por favor pare o outro coletor de dados ativo para coletar dados aqui.",\n          "no_sensors": "Nenhum sensor encontrado.",\n          "no_devices": "Nenhum dispositivo plugado.",\n          "not_connected": "Não contectado.",\n          "connecting": "Conectando...",\n          "connection_failed": "Conexão falhou.",\n          "connection_in_progress": "Conectando-se a seus sensores. Se uma mensagem surge sobre sensorconnector.concord.org, por favor, aceite -o",\n          "connection_failed_alert": "O Conector de Sensores do Concord Consortium não está instalado ou não está funcionando. Por favor __click_here_link__ obter instruções sobre a utilização do Conector de Sensores.",\n          "connection_failed_labquest2_alert": "Não foi possível conectar ao LabQuest2. Por favor verifique se o endereço está certo e se o LabQuest2 pode ser alcançado deste computador",\n          "tare_labquest2_alert": "O LabQuest2 precisa conectar dados ao vivo para zerar. Inicie uma nova corrida no LabQuest2 ou clique no ícone do medidor no alto à esquerda.",\n          "click_here": "clique aqui",\n          "connected": "Conectado.",\n          "connected_start_labquest2": "Conectado. Aperte iniciar no seu LabQuest2 to começar.",\n          "connected_start_sensorconnector": "Por favor pare o coletor de dados __controlling_client__ para coletar dados aqui.",\n          "connected_start_sensorconnector_noname": "Por favor pare o outro coletor de dados ativo para coletar dados aqui.",\n          "starting_data_collection": "Iniciando coleta de dados...",\n          "error_starting_data_collection": "Erro ao iniciar a coleta de dados.",\n          "error_starting_data_collection_alert": "Não foi possível iniciar a coleta de dados. Verifique se (remote starting) está habilitado",\n          "collecting_data": "Coletando dados.",\n          "collecting_data_stop_labquest2": "Coletando dados. Pressione parar no seu LabQuest2 para encerrar.",\n          "collecting_data_stop_sensorconnector": "Coletando dados.",\n          "no_data": "Não há dados disponíveis.",\n          "no_data_alert": "O Conector de Sensores parece não estar reportando dados ao dispositivo plugado",\n          "no_data_labquest2_alert": "The LabQuest parece não estar reportando dados ao dispositivo plugado",\n          "canceling_data_collection": "Cancelando coleta de dados...",\n          "error_canceling_data_collection": "Erro ao cancelar a coleta de dados.",\n          "error_canceling_data_collection_alert": "Não foi possível cancelar a coleta de dados. Verifique se (remote starting) está habilitado",\n          "stopping_data_collection": "Interrompendo a coleta de dados...",\n          "error_stopping_data_collection": "Erro ao interromper a coleta de dados.",\n          "error_stopping_data_collection_alert": "Não foi possível interromper a coleta de dados. Verifique se (remote starting) está habilitado",\n          "data_collection_stopped": "Coleta de dados interrompida.",\n          "data_collection_complete": "Coleta de dados completa.",\n          "disconnected": "Desconectado.",\n          "java_applet_error": "Parece que applets Java não podem rodar no seu navegador. Se você puder consertar isso, recarregue a página para usar o sensor.",\n          "java_applet_not_loading": "O applet do sensor parece não estar carregando. Se você puder consertar isso, recarregue a página para usar o sensor.",\n          "unexpected_error": "Houve um erro inesperado ao conectar com o sensor.",\n          "sensor_not_attached": "O __sensor_name__ parece não estar conectado. Tente reconectá-lo, e então clique \\"$t(sensor.messages.try_again)\\".",\n          "sensor_or_device_unplugged": "O __sensor_or_device_name__ estava deplugado. Tente reconectá-lo, e então clique \\"$t(sensor.messages.try_again)\\".",\n          "try_again": "Tentar de novo",\n          "cancel": "Cancelar"\n        },\n        "measurements": {\n          "sensor_reading": "Leitura do sensor",\n          "time": "Tempo",\n          "distance": "Distância",\n          "acceleration": "Aceleração",\n          "altitude": "Altitude",\n          "angle": "Ângulo",\n          "CO2": "CO₂",\n          "CO2_concentration": "Concentração de CO₂",\n          "charge": "Carga",\n          "conductivity": "Condutividade",\n          "current": "Corrente",\n          "dissolved_oxygen": "OD",\n          "flow_rate": "Taxa de fluxo",\n          "fluorescence_405_nm": "Fluorescência a 405 nm",\n          "fluorescence_500_nm": "Fluorescência a 500 nm",\n          "force": "Força",\n          "intensity": "Intensidade",\n          "light_level": "Nível de luz",\n          "light_intensity": "Intensidade de luz",\n          "magnetic_field": "Campo magnético",\n          "position": "Posição",\n          "potential": "Potencial",\n          "pressure": "Pressão",\n          "signal": "Sinal",\n          "sound_level": "Nível de som",\n          "speed": "Velocidade",\n          "temperature": "Temperatura",\n          "transmittance": "Transmitância",\n          "turbidity": "Turbidez",\n          "UV_intensity": "Intensidade UV",\n          "velocity": "Velocidade",\n          "volume": "Volume",\n          "pH": "pH",\n          "acidity": "Acidez",\n          "O2_concentration": "Concentração de O₂"\n        },\n        "names": {\n          "sensor": "sensor",\n          "no_sensor": "(sem sensor)",\n          "light": "Luz",\n          "motion": "Movimento",\n          "accelerometer": "Acelerômetro",\n          "dissolved_oxygen": "Oxigênio Dissolvido",\n          "pressure": "Pressão",\n          "charge_sensor": "Sensor de Carga",\n          "voltage": "Voltagem",\n          "pH": "pH",\n          "CO2_gas": "Gas CO₂",\n          "colorimeter": "Colorímetro",\n          "conductivity": "Condutividade",\n          "current": "Corrente",\n          "temperature": "Temperatura",\n          "force": "Força",\n          "anemometer": "Anemômetro",\n          "hand_dynamometer": "Dinamômetro de Mão",\n          "heart_rate": "Frequência Cardíaca",\n          "magnetic_field": "Campo Magnético",\n          "rotary_motion": "Movimento de Rotação",\n          "linear_position_sensor": "Sensor de Posição Linear",\n          "sound_level": "Nível de Som",\n          "spectrophotometer": "Espectrofotômetro",\n          "spirometer": "Espirômetro",\n          "turbidity": "Turbidez",\n          "UV_sensor": "Sensor UV",\n          "drop_counter": "Contador de Gotas",\n          "altitude": "Altitude",\n          "goMotion": "GoMotion",\n          "goTemp": "Sensor de Temperatura GoIO",\n          "goLinkTemperature": "Sensor de Temperatura GoIO",\n          "goLinkLight": "Sensor de Luz GoIO",\n          "goLinkForce": "Sensor de Força GoIO",\n          "goLinkPH": "Sensor de pH GoIO",\n          "goLinkCO2": "Sensor de  CO₂ GoIO",\n          "goLinkO2": "Sensor de  O₂ GoIO",\n          "labQuestMotion": "Sensor de Movimento LabQuest",\n          "labQuestTemperature": "Sensor de Temperatura LabQuest",\n          "labQuestLight": "Sensor de Luz LabQuest",\n          "labQuestForce": "Sensor de Força LabQuest",\n          "labQuestPH": "Sensor de pH LabQuest",\n          "labQuestCO2": "Sensor de CO₂ LabQuest",\n          "labQuestO2": "Sensor de O₂ LabQuest"\n        }\n      }\n    }\n  },\n  \n  "cs-CZ": {\n    "translation": {\n      "banner": {\n        "about": "O simulaci",\n        "about_tooltip": "Pokyny",\n        "share": "Sdílet",\n        "share_tooltip": "Sdílet pomocí e-mailu, IM nebo vložení do webové stránky",\n        "lang_tooltip": "Vybrat jazyk",\n        "reload_tooltip": "Opakovat simulaci",\n        "help_tooltip": "Zobrazit nápovědu",\n        "credits_tooltip": "Dozvědět se více o The Concord Consortium",\n        "fullscreen_tooltip": "Režim celé obrazovky",\n        "video_play_pause_tooltip": "Start / pauza simulace",\n        "video_reset_tooltip": "Obnova simulace",\n        "video_step_back_tooltip": "Krok zpět",\n        "video_step_forward_tooltip": "Krok vpřed",\n        "text_start": "Start",\n        "text_start_tooltip": "Start simulace nebo sběru dat",\n        "text_stop": "Stop",\n        "text_stop_tooltip": "Zastavit simulaci nebo sběr dat",\n        "text_reset": "Obnovit",\n        "text_reset_tooltip": "Obnovit simulaci nebo sběr dat",\n        "text_new_run": "Nový pokus",\n        "text_new_run_tooltip": "Nastavení nového pokusu",\n        "text_analyze_data": "Analyzovat data",\n        "text_analyze_data_tooltip": "Poslat data z experimentu do CODAP"\n      },\n      "dialog": {\n        "close_tooltip": "Zavřít"\n      },\n      "about_dialog": {\n        "title": "O simulaci: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "Sdílet: __interactive_title__",\n        "link": "odkaz",\n        "paste_email_im": "Vložit __link__ do e-mailu nebo IM.",\n        "paste_html": "HTML kód pro vložení do webové stránky nebo blogu",\n        "select_size": "Vybrat velikost:",\n        "size_larger": "__val__% vetší",\n        "size_actual": "současná",\n        "size_smaller": "__val__% menší"\n      },\n      "credits_dialog": {\n        "title": "Credits: __interactive_title__",\n        "credits_text": "Tato simulace byla vytvořena pomocí __CC_link__ využívající našeho __Next_Gen_MW_link__ softwaru. Práce byla financována z grantu __Google_link__.",\n        "shareable_ver": "Verze pro sdílení",\n        "find_shareable": "__shareable_ver_link__ s dalšími simulacemi z přírodních věd, matematiky a strojírenství je možné najít na __concord_org_link__."\n      },\n      "copyright": {\n        "copyright": "Copyright",\n        "all_rights_reserved": "Všechna práva vyhrazena.",\n        "license": "Software je licencován pod __MIT_link__.",\n        "attribution": "Prosím, zajistěte práva Concord Consortium a URL __concord_org_link__."\n      },\n      "md2d": {\n        "heatbath_icon_tooltip": "Vodní lázeň",\n        "ke_icon_tooltip": "Gradient kinetické energie",\n        "invalid_object_position_alert": "You can\'t drop the object there.",\n        "aminoacid_menu": {\n          "hydrophobic": "Hydrofobní",\n          "hydrophilic": "Hydrofilní",\n          "glycine": "Glycin",\n          "alanine": "Alanin",\n          "valine": "Valin",\n          "leucine": "Leucin",\n          "isoleucine": "Isoleucin",\n          "phenylalanine": "Fenylalanin",\n          "proline": "Prolin",\n          "tryptophan": "Tryptofan",\n          "methionine": "Methionin",\n          "cysteine": "Cystein",\n          "tyrosine": "Tyrosin",\n          "asparagine": "Asparagin",\n          "glutamine": "Glutamin",\n          "serine": "Serin",\n          "threonine": "Threonin",\n          "asparticacid": "Kyselina asparagová",\n          "glutamicacid": "Kyselina glutamová",\n          "lysine": "Lysin",\n          "arginine": "Arginin",\n          "histidine": "Histidin"\n        },\n        "mutations_menu": {\n          "substitution_mutation": "Substituční mutace",\n          "insertion_mutation": "Inzerce",\n          "deletion_mutation": "Delece",\n          "insert": "Vložení"\n        }\n      },\n      "sensor": {\n        "select_sensor": "Vybrat čidlo",\n        "select_sensor_type": "Vybrat druh čidla...",\n        "reading": "Hodnota:",\n        "zero": "Vynulovat",\n        "zeroing": "Nulování...",\n        "loading_sensor": "Nahrávání čidla...",\n        "choose_sensor_title": "Vybrat čidlo:",\n        "connect": "Připojeno",\n        "address_labquest2": "adresa LabQuest2",\n        "messages": {\n          "ready": "Připraven ke sběr dat.",\n          "ready_nocontrol": "Prosím, zastavte __controlling_client__ sběr dat, aby bylo možné sbírat data zde.",\n          "ready_nocontrol_noname": "Prosím, zastavte ostatní sběry dat, aby bylo možné sbírat data zde.",\n          "no_sensors": "Čidlo nenalezeno.",\n          "no_devices": "Není připojeno žádné zařízení.",\n          "not_connected": "Nepřipojeno.",\n          "connecting": "Připojování...",\n          "connection_in_progress": "Připojování čidel. Pokud se objeví zpráva o sensorconnector.concord.org, potvrďte ji, prosím.",\n          "connection_failed": "Připojení selhalo. __retry_link__",\n          "connection_failed_retry_link_text": "Zkusit znovu",\n          "connection_failed_alert": "Concord Consortium Sensor Connector není naistalován nebo spuštěn. Prosím, pro další instrukce k používání Sensor Connector __click_here_link__ .",\n          "connection_failed_labquest2_alert": "Nepodařilo se spojit s LabQuest2. Prosím, ověřte zda je adresa správná a jestli je možné se s LabQuest2 spojit z toho počítače.",\n          "tare_labquest2_alert": "Pro vynulování potřebuje LabQuest2 sbírat aktuální data. Buď nastavte nový pokus v LabQuest2, nebo klepněte na ikonu měření v levém horním rohu.",\n          "click_here": "Klepněte zde",\n          "connected": "Připojeno.",\n          "connected_start_labquest2": "Připojeno. Pro zahájení pokusu klepněte na start v LabQuest2.",\n          "connected_start_sensorconnector": "Prosím, zastavte __controlling_client__ sběr dat, aby bylo možné sbírat data zde.",\n          "starting_data_collection": "Začíná sběr dat...",\n          "error_starting_data_collection": "Chyba při zahájení sběru dat.",\n          "error_starting_data_collection_alert": "Nebylo možné zahájit sběr dat. Ujistěte se, že (remote starting) je povolený.",\n          "collecting_data": "Sbírám data.",\n          "collecting_data_stop_labquest2": "Sbírám data. Klikněte na stop v LabQuest2, aby se sběr ukončil.",\n          "collecting_data_stop_sensorconnector": "Sběr dat.",\n          "no_data": "Data nejsou k dispozici.",\n          "no_data_alert": "Sensor Connector nezaznamenává data ze zapojeného zařízení.",\n          "no_data_labquest2_alert": "LabQuest nezaznamenává data ze zapojeného zařízení.",\n          "canceling_data_collection": "Rušení sběru dat...",\n          "error_canceling_data_collection": "Chyba při rušení sběru dat.",\n          "error_canceling_data_collection_alert": "Není možné zrušit sběr dat. Ujistěte se, že (remote starting) je povolený.",\n          "stopping_data_collection": "Zastavování sběru dat...",\n          "error_stopping_data_collection": "Chyba při zastavování sběru dat.",\n          "error_stopping_data_collection_alert": "Není možné zastavit sběr dat. Ujistěte se, že (remote starting) je povolený.",\n          "data_collection_stopped": "Sběr dat zastaven.",\n          "data_collection_complete": "Sběr dat dokončen.",\n          "disconnected": "Odpojeno.",\n          "java_applet_error": "Java aplety nemohou být spuštěny v prohlížeči. Po opravě znovu načtěte stránku, aby bylo možné použít sezor.",\n          "java_applet_not_loading": "čidlo se nenačítá. Po opravě znovu načtěte stránku, aby bylo možné použít sezor.",\n          "unexpected_error": "Nastala neočekáváná chyba při připojování čidla.",\n          "sensor_not_attached": "__sensor_name__ není připojený. Zkuste ho připojit znovu a poté klikněte na \\"$t(sensor.messages.try_again)\\".",\n          "sensor_or_device_unplugged": " __sensor_or_device_name__ byl odpojen. Zkuste ho připojit znovu a poté klepněte na \\"$t(sensor.messages.try_again)\\".",\n          "try_again": "Zkusit znovu",\n          "cancel": "Zrušit"\n        },\n        "measurements": {\n          "sensor_reading": "Hodnota čidla",\n          "time": "Čas",\n          "distance": "Vzdálenost",\n          "acceleration": "Zrychlení",\n          "altitude": "Výška",\n          "angle": "Úhel",\n          "CO2": "CO2",\n          "CO2_concentration": "Koncentrace CO2",\n          "charge": "Náboj",\n          "conductivity": "Vodivost",\n          "current": "Proud",\n          "dissolved_oxygen": "DO - rozpuštěný kyslík",\n          "flow_rate": "Objemový průtok",\n          "fluorescence_405_nm": "Fluorescence 405 nm",\n          "fluorescence_500_nm": "Fluorescence 500 nm",\n          "force": "Síla",\n          "intensity": "Intenzita",\n          "light_level": "Množství světla",\n          "light_intensity": "Intenzita osvětlení",\n          "magnetic_field": "Magnetické pole",\n          "position": "Pozice",\n          "potential": "Potenciál",\n          "pressure": "Tlak",\n          "signal": "Signál",\n          "sound_level": "Hlasitost",\n          "speed": "Rychlost",\n          "temperature": "Teplota",\n          "transmittance": "Transmitance",\n          "turbidity": "Turbidita (zákal)",\n          "UV_intensity": "Intenzita UV záření",\n          "velocity": "Rychlost",\n          "volume": "Objem",\n          "pH": "pH",\n          "acidity": "Kyselost",\n          "O2_concentration": "Koncentrace O2"\n        },\n        "names": {\n          "sensor": "čidlo",\n          "no_sensor": "(žádné čidlo)",\n          "light": "Světlo",\n          "motion": "Pohyb",\n          "accelerometer": "Akcelerometer (čidlo zrychlení)",\n          "dissolved_oxygen": "Rozpuštěný kyslík",\n          "pressure": "Tlak",\n          "charge_sensor": "Elektroskop (čidlo náboje)",\n          "voltage": "Elektrické napětí",\n          "pH": "pH",\n          "CO2_gas": "CO2",\n          "colorimeter": "Kolorimetr",\n          "conductivity": "Vodivost",\n          "current": "Elektrický proud",\n          "temperature": "Teplota",\n          "force": "Síla",\n          "anemometer": "Anemometr",\n          "hand_dynamometer": "Ruční siloměr",\n          "heart_rate": "Srdeční tep",\n          "magnetic_field": "Magnetické pole",\n          "rotary_motion": "Rotační pohyb",\n          "linear_position_sensor": "Lineární poloha",\n          "sound_level": "Hlasitost",\n          "spectrophotometer": "Spektrofotometr",\n          "spirometer": "Spirometr",\n          "turbidity": "Turbidita (zákal)",\n          "UV_sensor": "Čidlo UV záření",\n          "drop_counter": "Čítač kapek",\n          "altitude": "Výška",\n          "goMotion": "GoMotion",\n          "goTemp": "GoIO teplotní čidlo",\n          "goLinkTemperature": "GoIO teplotní čidlo",\n          "goLinkLight": "GoIO světelné čidlo",\n          "goLinkForce": "GoIO čidlo pro měření síly",\n          "goLinkPH": "GoIO čidlo pH",\n          "goLinkCO2": "GoIO čidlo pro měžení CO2",\n          "goLinkO2": "GoIO čidlo pro měření O2",\n          "labQuestMotion": "LabQuest pohybové čidlo",\n          "labQuestTemperature": "LabQuest teplotní čidlo",\n          "labQuestLight": "LabQuest světelné čidlo",\n          "labQuestForce": "LabQuest čidlo pro měření síly",\n          "labQuestPH": "LabQuest čidlo pH",\n          "labQuestCO2": "LabQuest čidlo pro měření CO2",\n          "labQuestO2": "LabQuest čidlo pro měření O2"\n        }\n      }\n    }\n  },\n  \n  "es": {\n    "translation": {\n      "banner": {\n        "about": "Acerca de",\n        "about_tooltip": "Instrucciones",\n        "share": "Compartir",\n        "share_tooltip": "Compartir a través de e-mail, MI o embeber en sitio web",\n        "lang_tooltip": "Elegir idioma",\n        "reload_tooltip": "Recargar el interactivo",\n        "help_tooltip": "Mostrar ayuda",\n        "credits_tooltip": "Saber más sobre The Concord Consortium",\n        "fullscreen_tooltip": "Cambiar a pantalla completa",\n        "video_play_pause_tooltip": "Iniciar / pausar la simulación",\n        "video_reset_tooltip": "Resetear la simulación",\n        "video_step_back_tooltip": "Un paso atrás",\n        "video_step_forward_tooltip": "Un paso adelante",\n        "text_start": "Iniciar",\n        "text_start_tooltip": "Iniciar la simulación o recolección de datos",\n        "text_stop": "Detener",\n        "text_stop_tooltip": "Detener la simulación o recolección de datos",\n        "text_reset": "Resetear",\n        "text_reset_tooltip": "Resetear la simulación o recolección de datos"\n      },\n      "dialog": {\n        "close_tooltip": "Cerrar"\n      },\n      "about_dialog": {\n        "title": "Acerca de: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "Compartir: __interactive_title__",\n        "link": "enlace",\n        "paste_email_im": "Pegar este __link__ en email o MI",\n        "paste_html": "Pegar HTML para embeber en sitio web o blog.",\n        "select_size": "Elegir tamaño:",\n        "size_larger": "__val__% más grande",\n        "size_actual": "actual",\n        "size_smaller": "__val__% más pequeño"\n      },\n      "credits_dialog": {\n        "title": "Créditos: __interactive_title__",\n        "credits_text": "Este interactivo fue creado por the __CC_link__ usando nuestro software __Next_Gen_MW_link__, con fondos provenientes de un gran de __Google_link__.",\n        "shareable_ver": "versión para compartir",\n        "find_shareable": "Buscar un __shareable_ver_link__ de este interactivo junto a docenas de otros interactivos de código abierto para ciencias, matemática e ingeniería en __concord_org_link__."\n      },\n      "copyright": {\n        "copyright": "Copyright",\n        "all_rights_reserved": "Todos los derechos reservados.",\n        "license": "Este software está licenciado bajo licencia de __MIT_link__.",\n        "attribution": "Por favor dar atribución a The Concord Consortium y la URL __concord_org_link__."\n      }\n    }\n  },\n  \n  "ru": {\n    "translation": {\n      "banner": {\n        "about": "Об интерактиве",\n        "about_tooltip": "Инструкция",\n        "share": "Опубликовать ссылку",\n        "share_tooltip": "Поделиться данными в e-mail, IM или вставить их на страницу в web-страницу",\n        "lang_tooltip": "Выбрать язык",\n        "reload_tooltip": "Перезагрузить интерактив",\n        "help_tooltip": "Показать подсказку",\n        "credits_tooltip": "Подробнее о Конкорд Консорциуме",\n        "fullscreen_tooltip": "Переключить на полноэкранную версию",\n        "video_play_pause_tooltip": "Старт/пауза симуляции",\n        "video_reset_tooltip": "Перезагрузить симуляцию",\n        "video_step_back_tooltip": "Шаг назад",\n        "video_step_forward_tooltip": "Шаг вперед",\n        "text_start": "Старт",\n        "text_start_tooltip": "Запустить симуляции или сбор данных",\n        "text_stop": "Стоп",\n        "text_stop_tooltip": "Остановить симуляцию или сбор данных ",\n        "text_reset": "Перегрузить",\n        "text_reset_tooltip": " Перегрузить симуляцию или сбор данных",\n        "text_new_run": "Повторный запуск",\n        "text_new_run_tooltip": "Повторить запуск нового эксперимента",\n        "text_analyze_data": "Анализ данных",\n        "text_analyze_data_tooltip": "Послать данные опыта на CODAP"\n      },\n      "dialog": {\n        "close_tooltip": "Закрыть"\n      },\n      "about_dialog": {\n        "title": "Об интерактиве: __interactive_title__"\n      },\n      "share_dialog": {\n        "title": "Поделиться: __interactive_title__",\n        "link": " линк ",\n        "paste_email_im": "Вставить этот __link__ в e-mail или IM.",\n        "paste_html": " Скопировать HTML для вставки в web-site или блог.",\n        "select_size": "Выбрать размер:",\n        "size_larger": "__val__% больше",\n        "size_actual": "actual",\n        "size_smaller": "__val__% меньше"\n      },\n      "credits_dialog": {\n        "title": "Авторство: __interactive_title__",\n        "credits_text": "Данные интерактивные  учебные продукты были созданы __CC_link__ с использованием нашего __Next_Gen_MW_link__ программного продукта, при финансовой поддержке  за счет гранта от __Google_link__.",\n        "shareable_ver": "версия для обмена",\n        "find_shareable": "Найти __shareable_ver_link__ коллекцию интерактивных учебных продуктов наряду с десятками  других открытых программных продуктов по естественным наукам, математике и инжинирингу в материалах __concord_org_link__."\n      },\n      "copyright": {\n        "copyright": "Авторские права",\n        "all_rights_reserved": "Все права защищены.",\n        "license": "Лицензия на данный программный продукт предоставляется в соответствии с __MIT_link__ лицензией.",\n        "attribution": "Просьба при использовании данного продукта ссылаться на Конкорд Консорциум URL __concord_org_link__."\n      },\n      "md2d": {\n        "heatbath_icon_tooltip": "Тепловая баня активна",\n        "ke_icon_tooltip": "Градиент кинетической энергии",\n        "invalid_object_position_alert": "Нельзя сбросить объект здесь.",\n        "aminoacid_menu": {\n          "hydrophobic": "Гидрофобный",\n          "hydrophilic": "Гидрофильный",\n          "glycine": "Глицин",\n          "alanine": "Аланин",\n          "valine": "Валин",\n          "leucine": "Лейцин",\n          "isoleucine": "Изолейцин",\n          "phenylalanine": "Фенилаланин",\n          "proline": "Пролин",\n          "tryptophan": "Триптофан",\n          "methionine": "Метионин",\n          "cysteine": "Цистеин",\n          "tyrosine": "Тирозин",\n          "asparagine": "Аспарагин",\n          "glutamine": "Глутамин",\n          "serine": "Серин",\n          "threonine": "Треонин",\n          "asparticacid": "Аспартат",\n          "glutamicacid": "Глутамат",\n          "lysine": "Лизин",\n          "arginine": "Аргинин",\n          "histidine": "Гистидин"\n        },\n        "mutations_menu": {\n          "substitution_mutation": "Мутация: Замена нуклеотида",\n          "insertion_mutation": "Мутация: Вставка нуклеотида",\n          "deletion_mutation": " Мутация: Удаление нуклеотида ",\n          "insert": "Вставить"\n        }\n      },\n      "sensor": {\n        "select_sensor": "Выбрать датчик",\n        "select_sensor_type": "Выбрать тип датчика…",\n        "reading": "Съем данных:",\n        "zero": "Ноль",\n        "zeroing": "Выбор ноля...",\n        "loading_sensor": "Загрузка датчика...",\n        "choose_sensor_title": "Выбор датчика:",\n        "connect": "Соединить",\n        "address_labquest2": "адрес LabQuest2",\n        "messages": {\n          "ready": "Готов к сбору данных.",\n          "ready_nocontrol": "Пожалуйста остановите __controlling_client__ сборщик данных будет собирать данные здесь.",\n          "ready_nocontrol_noname": "Пожалуйста остановите все остальные активные сборщики данных, чтобы собрать данные здесь.",\n          "no_sensors": "Ни один датчик не найден.",\n          "no_devices": "Ни одно устройство не подключено.",\n          "not_connected": "Нет соединения.",\n          "connecting": "Идет подсоединение...",\n          "connection_in_progress": " Идет подсоединение к вашему датчику. Если придет сообщение от sensorconnector.concord.org, пожалуйста примите его.",\n          "connection_failed": "Подсоединения не произошло. __retry_link__",\n          " Подсоединение_failed_retry_link_text": "Повторить попытку",\n          "подсоединения _failed_внимание": "Программа для подсоединения датчиков Concord Consortium Sensor Connector либо не инсталлирована, либо не подключена. Пожалуйста __click_here_link__ для получения инструкции по использованию программы Sensor Connector.",\n          "connection_failed_labquest2_alert": "Не произошло подсоединение к LabQuest2. Пожалуйста убедитесь, в правильности адреса и в том, что LabQuest2 доступен на данном компьютере",\n          "tare_labquest2_alert": "LabQuest2 должен начать сбор данных, чтобы выставить значение ноля. Запустите новое измерение на LabQuest2 или нажмите на иконку измерительного устройства в левом верхнем углу экрана.",\n          "click_here": "Нажать здесь",\n          "connected": "Подсоединение произошло.",\n          "connected_start_labquest2": " Подсоединение произошло. Чтобы начать работу, нажмите кнопку Старт на вашем LabQuest2.",\n          "connected_start_sensorconnector": "Пожалуйста остановите __controlling_client__ сборщик данных чтобы собрать данные здесь.",\n          "connected_start_sensorconnector_noname": "Пожалуйста остановите  другие сборщики данных чтобы собрать данные здесь.",\n          "starting_data_collection": "Начать сбор данных...",\n          "ошибка_starting_data_collection": "Ошибка при запуске сбора данных.",\n          "error_starting_data_collection_alert": " Не запускается сбор данных. Убедитесь в том, что (удаленный старт) разрешен ",\n          "collecting_data": "Идет сбор данных.",\n          "collecting_data_stop_labquest2": " Идет сбор данных. Чтобы его завершит, нажмите кнопку Стоп на LabQuest2.",\n          "collecting_data_stop_sensorconnector": " Идет сбор данных.",\n          "no_data": "Данные не доступны.",\n          "no_data_alert": "Похоже, что The Sensor Connector не передает данные для подключенных устройств",\n          "no_data_labquest2_alert": " Похоже, что LabQuest не передает данные для подключенных устройств",\n          "canceling_data_collection": "Отмена сбора данных...",\n          "error_canceling_data_collection": "Ошибка при отмене сбора данных.",\n          "error_canceling_data_collection_alert": "Не получается отменить сбор данных. Убедитесь в том, что (удаленный старт) разрешен",\n          "stopping_data_collection": "Происходит остановка сбора данных...",\n          "error_stopping_data_collection": " Ошибка при остановке сбора данных.",\n          "error_stopping_data_collection_alert": "Не удалось остановить сбор данных. Убедитесь в том, что (удаленный старт) разрешен ",\n          "data_collection_stopped": "Сбор данных остановлен.",\n          "data_collection_complete": " Сбор данных завершен.",\n          "disconnected": "Произошло отсоединение.",\n          "java_applet_error": "Похоже на то, что Java апплеты не поддерживаются вашим браузером. Попытайтесь решить эту проблему, а затем перезагрузите страницу для того, чтобы использовать датчики",\n          "java_applet_not_loading": " Похоже на то, что апплеты датчиков не загружаются. Попытайтесь решить эту проблему, а затем перезагрузите страницу для того, чтобы использовать датчики",\n          "unexpected_error": "Произошла непредвиденная ошибка при подсоединении к датчику.",\n          "sensor_not_attached": "Похоже, что __sensor_name__ не прикрепиля. Пожалуйста повторите попытку и затем кликните на \\"$t(sensor.messages.try_again)\\".",\n          "датчик_or_device_не подключен": "The __sensor_or_device_name__ был отключен. Пожалуйста подключите его и затем кликните \\"$t(sensor.messages.try_again)\\".",\n          "try_again": "Повторить попытку",\n          "cancel": "Отменить"\n        },\n        "measurements": {\n          "sensor_reading": "Идет считывание датчика",\n          "time": "Время",\n          "distance": "Расстояние",\n          "acceleration": "Ускорение",\n          "altitude": "Высота над уровнем моря",\n          "angle": "Угол",\n          "CO2": "CO₂",\n          "CO2_concentration": "CO₂ Концентрация",\n          "charge": "Заряд",\n          "conductivity": "Проводимость",\n          "current": "Ток",\n          "dissolved_oxygen": "Растворенный кислород",\n          "flow_rate": "Скорость потока",\n          "fluorescence_405_nm": "Флюоресценция 405 nm",\n          "fluorescence_500_nm": "Флюоресценция 500 nm",\n          "force": "Сила",\n          "intensity": "Интенсивность",\n          "light_level": "Уровень освещенности",\n          "light_intensity": " Интенсивность освещенности ",\n          "magnetic_field": "Магнитное поле",\n          "position": "Расположение",\n          "potential": "Потенциал",\n          "pressure": "Давление",\n          "signal": "Сигнал",\n          "sound_level": "Уровень шума",\n          "speed": "Скорость",\n          "temperature": "Температура",\n          "transmittance": "Пропускание",\n          "turbidity": "Мутность",\n          "UV_intensity": "Интенсивность УФ",\n          "velocity": "Вектор скорости",\n          "volume": "Объем",\n          "pH": "рН",\n          "acidity": "Кислотность",\n          "O2_concentration": " Концентрация O₂"\n        },\n        "names": {\n          "sensor": "датчик",\n          "no_sensor": "(нет датчика)",\n          "light": "Освещенность",\n          "motion": "Перемещение",\n          "accelerometer": "Измеритель ускорения",\n          "dissolved_oxygen": "Растворенный кислород",\n          "pressure": "Давление",\n          "charge_sensor": "Сменить датчик",\n          "voltage": "Напряжение",\n          "pH": "рН",\n          "CO2_gas": "CO₂ газ",\n          "colorimeter": "Колориметр",\n          "conductivity": "Проводимость",\n          "current": "Ток",\n          "temperature": "Температура",\n          "force": "Сила",\n          "anemometer": "Анемометр",\n          "hand_dynamometer": "Ручной динамометр",\n          "heart_rate": "Частота сердечных сокращений",\n          "magnetic_field": "Магнитное поле",\n          "rotary_motion": "Угловое перемещение",\n          "linear_position_sensor": "Датчик прямолинейного перемещения",\n          "sound_level": "Уровень шума",\n          "spectrophotometer": "Спектрофотометр",\n          "spirometer": "Спирометр ",\n          "turbidity": "Мутность",\n          "UV_sensor": "УФ датчик ",\n          "drop_counter": "Счетчик капель",\n          "altitude": "Высота над уривнем моря",\n          "goMotion": "GoMotion",\n          "goTemp": "GoIO температурный датчик",\n          "goLinkTemperature": "GoIO температурный датчик",\n          "goLinkLight": "GoIO оптический датчик ",\n          "goLinkForce": "GoIO датчик силы ",\n          "goLinkPH": "GoIO pH датчик ",\n          "goLinkCO2": "GoIO CO₂ датчик ",\n          "goLinkO2": "GoIO O₂ датчик",\n          "labQuestMotion": "LabQuest датчик перемещения",\n          "labQuestTemperature": "LabQuest температурный датчик ",\n          "labQuestLight": "LabQuest оптический датчик ",\n          "labQuestForce": "LabQuest датчик силы ",\n          "labQuestPH": "LabQuest pH датчик ",\n          "labQuestCO2": "LabQuest CO₂ датчик ",\n          "labQuestO2": "LabQuest O₂ датчик"\n        }\n      }\n    }\n  }\n\n}\n';});

/*global define */

define('common/i18n',['require','i18next','lab-grapher','text!locales/translations.json'],function (require) {
  var i18next       = require('i18next');
  var LabGrapher    = require('lab-grapher');
  var translations  = JSON.parse(require('text!locales/translations.json'));

  return function i18n(language) {
    i18next.init({
      lng: language,
      resStore: translations,
      fallbackLng: 'en-US',
      useCookie: false
    });
    // Grapher has its own i18n support implemented, just set language.
    LabGrapher.i18n.lang = language;
    return i18next;
  };
});

/*global define: false */

define('common/controllers/interactive-metadata',[],function() {

  return {
    /**
      Interactive top-level properties:
    */
    interactive: {
      title: {
        required: true
      },

      publicationStatus: {
        defaultValue: "public"
      },

      labEnvironment: {
        // An indicator of which Lab environment the interactive is compatible with.
        // Possible values:
        // - "production"
        // - "staging"
        // - "development"
        defaultValue: "production"
      },

      // Optional path to metadata containing information about available translations.
      // If present and valid, a new pulldown will be added to interactive UI that lets user
      // change language and locales.
      i18nMetadata: {
        defaultValue: ""
      },

      lang: {
        defaultValue: "en-US"
      },

      theme: {
        // Theme name or array of theme names. Multiple themes can be applied at the same time.
        // Note that theme is just a CSS class added to the interactive container, for example
        // ["foo", "bar"] will add following classes: .lab-theme-foo, .lab-theme-bar
        defaultValue: ""
      },

      showTopBar: {
        // Reload, share, about and language.
        defaultValue: true
      },

      showBottomBar: {
        // CC Logo and full-screen mode.
        defaultValue: true
      },

      credits: {
        // Content of the credits dialog. If it's not specified, the default, translatable text will be used.
        defaultValue: ''
      },

      padding: {
        // Top, bottom and left interactive padding, but NOT right...
        // This option was defined that way long time ago and now it has been exposed to authors.
        // We couldn't support right padding at the moment, as we would break backward compatibility.
        defaultValue: 10
      },

      subtitle: {
        defaultValue: ""
      },

      about: {
        defaultValue: ""
      },

      // optional: used by activity finder (pt: http://bit.ly/IGmyks)
      category: {
        defaultValue: ""
      },

      // optional: used by activity finder (pt: http://bit.ly/IGmyks)
      subCategory: {
        defaultValue: ""
      },

      // optional: used by activity finder (pt: http://bit.ly/IGpo96)
      screenshot: {
        defaultValue: ""
      },

      // optional: holds path of html or cml page this Interactive was imported from
      importedFrom: {},

      aspectRatio: {
        defaultValue: 1.3
      },

      fontScale: {
        defaultValue: 1
      },

      randomSeed: {
        required: false
      },

      helpOnLoad: {
        // If true, the help mode will be automatically shown on interactive load.
        defaultValue: false
      },

      aboutOnLoad: {
        // If true, the About dialog will be automatically shown on interactive load.
        defaultValue: false
      },

      models: {
        // List of model definitions. Its definition is below ('model').
        required: true
      },

      parameters: {
        // List of custom parameters.
        defaultValue: []
      },

      dataSets: {
        // List of data sets.
        defaultValue: []
      },

      propertiesToRetain: {
        // List of properties that should be retained during model reload or reset.
        defaultValue: []
      },

      outputs: {
        // List of outputs.
        defaultValue: []
      },

      filteredOutputs: {
        // List of filtered outputs.
        defaultValue: []
      },

      experiment: {
        required: false
      },

      exports: {
        required: false
      },

      logging: {
        // Note that logging is enabled by default, even if configuration is not provided. Take a look at
        // logging section below to see the default configuration. To disable logging, you need to explicitly
        // provide config with "enabled" property set to false.
        required: false
      },

      components: {
        // List of the interactive components. Their definitions are below ('button', 'checkbox' etc.).
        defaultValue: []
      },

      layout: {
        // Layout definition.
        defaultValue: {}
      },

      template: {
        // Layout template definition.
        defaultValue: "simple"
      },

      helpTips: {
        // List of help tips. See 'helpTip' metadata.
        defaultValue: []
      }
    },

    model: {
      // Definition of a model.
      // Can include either a URL to model definition or model options hash..
      type: {
        required: true
      },
      id: {
        required: true
      },
      url: {
        conflictsWith: ["model"]
      },
      // optional: holds path of html or cml page this Interactive was imported from
      importedFrom: {},
      model: {
        conflictsWith: ["url"]
      },
      // Optional "onLoad" script.
      onLoad: {},
      // Optional hash of options overwriting model options.
      viewOptions: {},
      modelOptions: {},
      // Parameters, outputs and filtered outputs can be also specified per model.
      parameters: {},
      outputs: {},
      filteredOutputs: {}
    },

    parameter: {
      name: {
        required: true
      },
      initialValue: {
        required: true
      },
      // Optional "onChange" script.
      onChange: {},
      // Optional description.
      label: {},
      unitType: {},
      unitName: {},
      unitPluralName: {},
      unitAbbreviation: {}
    },

    output: {
      name: {
        required: true
      },
      value: {
        required: true
      },
      // Optional description.
      label: {},
      unitType: {},
      unitName: {},
      unitPluralName: {},
      unitAbbreviation: {}
    },

    dataSet: {
      name: {
        required: true
      },
      properties: {
        defaultValue: []
      },
      serializableProperties: {
        // You can provide a list of properties that should be serialized, e.g.:
        // ["prop1", "prop2", "time"]
        // or use special values: "all" or "none".
        defaultValue: "all"
      },
      streamDataFromModel: {
        defaultValue: true
      },
      clearOnModelReset: {
        // Note that "model reset" in general includes actions like:
        // - reset
        // - reload
        // - new model load
        defaultValue: true
      },
      initialData: {}
    },

    filteredOutput: {
      name: {
        required: true
      },
      property: {
        required: true
      },
      type: {
        // For now, only "RunningAverage" is supported.
        defaultValue: "RunningAverage"
      },
      period: {
        // Smoothing time period in fs.
        defaultValue: 2500
      },
      // Optional description.
      label: {},
      unitType: {},
      unitName: {},
      unitPluralName: {},
      unitAbbreviation: {}
    },

    exports: {
      selectionComponents: {
        required: false,
        defaultValue: []
      },
      perRun: {
        required: false,
        defaultValue: []
      },
      perTick: {
        required: true
      }
    },

    logging: {
      enabled: {
        // Global logging switch. Model start, stop and reload are be logged by default
        // (and probably some other events in the future). Also, #logAction scripting API function
        // works only if it's set to true.
        defaultValue: true
      },
      properties: {
        // Properties that are logged on start, stop and reload events.
        // "boundToComponents" is a special value for authors' convenience.
        defaultValue: "boundToComponents"
      },
      components: {
        // List of components which should log user interaction.
        // "all" and "none" are special values for authors' convenience.
        defaultValue: "all"
      }
    },

    /**
      Interactive experiment template:
    */
    experiment: {
      timeSeries: {
        required: true
      },
      parameters: {
        required: true,
        defaultValue: []
      },
      destinations: {
        required: true,
        defaultValue: []
      },
      stateButtons: {
        required: true,
        startRun: {
          required: true,
          defaultValue: "start-run"
        },
        stopRun: {
          required: true,
          defaultValue: "stop-run"
        },
        saveRun: {
          required: true,
          defaultValue: "save-run"
        },
        nextRun: {
          required: true,
          defaultValue: "next-run"
        },
        clearAll: {
          required: true,
          defaultValue: "clear-all"
        }
      },
      onReset: {
      },
      savedRuns: {
        defaultValue: []
      }
    },

    experimentTimeSeries: {
      time: {
        defaultValue: "displayTime"
      },
      properties: {
        required: true,
        defaultValue: []
      }
    },

    experimentParameter: {
      inputs: {
        required: true,
        defaultValue: []
      },
      outputs: {
        required: true,
        defaultValue: []
      }
    },

    experimentDestination: {
      type: {
        required: true
      },
      componentIds: {
        required: true,
        defaultValue: []
      },
      properties: {
        required: true,
        defaultValue: []
      }
    },

    experimentSavedRun: {
      timeStamp: {
        required: true
      },
      timeSeries: {
        required: true,
        defaultValue: []
      },
      parameters: {
        required: true,
        defaultValue: []
      }
    },

    /**
      Interactive components:
    */
    playback: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      stepping: {
        defaultValue: true
      }
    },

    text: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      onClick: {
        // Script executed on user click, optional.
      },
      text: {
        // Text content.
        defaultValue: ""
      },
      width: {
        defaultValue: "auto"
      },
      height: {
        defaultValue: "auto"
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    image: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      src: {
        // Absolute path should start with http(s)://
        // Relative path is relative to model URL, unless specified
        // by urlRelativeTo.
        defaultValue: ""
      },
      urlRelativeTo: {
        // Specifies the url with which relative urls in src are resolved.
        // Possible values: model, page
        defaultValue: "model"
      },
      width: {
        defaultValue: "auto"
      },
      height: {
        defaultValue: "auto"
      },
      onClick: {
        // Script executed on user click, optional.
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    div: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      content: {
        conflictsWith: ["url"]
      },
      url: {
        conflictsWith: ["content"]
      },
      width: {
        defaultValue: "auto"
      },
      height: {
        defaultValue: "auto"
      },
      onClick: {
        // Script executed on user click, optional.
      },
      classes: {
        defaultValue: []
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    button: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      action: {
        required: true
      },
      text: {
        defaultValue: ""
      },
      width: {
        defaultValue: ""
      },
      height: {
        defaultValue: ""
      },
      disabled: {
        defaultValue: false
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    checkbox: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      text: {
        defaultValue: ""
      },
      textOn: {
        defaultValue: "right"
      },
      width: {
        defaultValue: "auto"
      },
      height: {
        defaultValue: "auto"
      },
      property: {
        conflictsWith: ["initialValue", "action"]
      },
      retainProperty: {
        // If property binding is used (so 'property' is defined), this flag decides whether
        // property should be retained during model reload / reset or not.
        defaultValue: true
      },
      action: {
        // Script executed when checkbox is changed, optional.
        conflictsWith: ["property"]
      },
      initialValue: {
        // Note that 'initialValue' makes sense only for checkboxes without property binding.
        // Do not use checkbox as setter of a given property.
        conflictsWith: ["property"]
      },
      disabled: {
        defaultValue: false
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    slider: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      min: {
        required: true
      },
      max: {
        required: true
      },
      steps: {
        required: true
      },
      title: {
        defaultValue: ""
      },
      titlePosition: {
        defaultValue: "top" // valid options: top, bottom, left, right
      },
      fillColor: {},
      fillToValue: {},
      labels: {
        // Label is specified by the following object:
        // {
        //   "value": [number or "left" or "right"],
        //   "label": [label, e.g. "High"]
        // }
        // Note that a label with "value": "left" (or "right") will be displayed to the left (or right) of the slider,
        // instead of underneath.
        defaultValue: []
      },
      width: {
        defaultValue: "auto"
      },
      height: {
        defaultValue: "auto"
      },
      displayValue: {},
      // Use "property" OR "action" + "initialValue".
      property: {
        // If you use property binding, do not mix it with action scripts and initial values.
        conflictsWith: ["initialValue", "action"]
      },
      retainProperty: {
        // If property binding is used (so 'property' is defined), this flag decides whether
        // property should be retained during model reload / reset or not.
        defaultValue: true
      },
      action: {
        conflictsWith: ["property"]
      },
      initialValue: {
        // Do not use slider as a property setter.
        // There are better ways to do it, e.g.:
        // "onLoad" scripts (and set({ }) call inside), "modelOptions", etc.
        conflictsWith: ["property"]
      },
      disabled: {
        defaultValue: false
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    pulldown: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      label: {
        defaultValue: ""
      },
      labelOn: {
        // Other option is "left".
        defaultValue: "top"
      },
      options: {
        defaultValue: []
      },
      property: {
        // Pulldown can be also connected to a model property.
        // In such case, options should define "value", not "action".
      },
      retainProperty: {
        // If property binding is used (so 'property' is defined), this flag decides whether
        // property should be retained during model reload / reset or not.
        defaultValue: true
      },
      disabled: {
        defaultValue: false
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    pulldownOption: {
      text: {
        defaultValue: ""
      },
      action: {
        // Use it when pulldown is not bound to any model property.
        conflictsWith: ["value"]
      },
      value: {
        // Use it when pulldown is bound to some model property.
        conflictsWith: ["action"]
      },
      selected: {
        // Use it when pulldown is not bound to any model property.
        // When "property" is used for pulldown, it will determine
        // selection.
        conflictsWith: ["value"]
      },
      disabled: {}
    },

    radio: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      style: {
        // "radio" (classic radio button) or "toggle" (looks like group of regular buttons).
        defaultValue: "radio"
      },
      orientation: {
        defaultValue: "vertical"
      },
      label: {
        defaultValue: ""
      },
      labelOn: {
        // Other option is "left".
        defaultValue: "top"
      },
      options: {
        defaultValue: []
      },
      property: {
        // Radio can be also connected to a model property.
        // In such case, options should define "value", not "action".
      },
      retainProperty: {
        // If property binding is used (so 'property' is defined), this flag decides whether
        // property should be retained during model reload / reset or not.
        defaultValue: true
      },
      disabled: {
        defaultValue: false
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    radioOption: {
      text: {
        defaultValue: ""
      },
      action: {
        // Use it when radio is not bound to any model property.
        conflictsWith: ["value"]
      },
      value: {
        // Use it when radio is bound to some model property.
        conflictsWith: ["action"]
      },
      selected: {
        // Use it when radio is not bound to any model property.
        // When "property" is used for radio, it will determine
        // selection.
        conflictsWith: ["value"]
      },
      disabled: {}
    },

    numericOutput: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      property: {
        required: true
      },
      label: {
        defaultValue: ""
      },
      units: {
        defaultValue: ""
      },
      orientation: {
        defaultValue: "horizontal"
      },
      width: {
        defaultValue: "auto"
      },
      height: {
        defaultValue: "auto"
      },
      displayValue: {},
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    thermometer: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      min: {
        required: true
      },
      max: {
        required: true
      },
      width: {
        // It controls width of the thermometer graphics!
        // It won't affect label, e.g. making it truncated
        // as width is only "2.5em".
        defaultValue: "2.5em"
      },
      height: {
        // Height of the whole thermometer with reading.
        defaultValue: "100%"
      },
      labelIsReading: {
        defaultValue: false
      },
      reading: {
        defaultValue: {
          units: "K",
          offset: 0,
          scale: 1,
          digits: 0
        }
      },
      labels: {
        // Label is specified by the following object:
        // {
        //   "value": [value, e.g. 100],
        //   "label": [label, e.g. "High"]
        // }
        defaultValue: []
      }
    },

    joystick: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      title: {
        defaultValue: ""
      },
      labels: {
        // Labels is specified by the following object:
        // {
        //   n: 'North'
        //   w: 'West'
        //   s: 'South'
        //   e: 'East'
        // }
        defaultValue: {n: 'N', e: 'E', s: 'S', w: 'W'}
      },
      scale: {
        defaultValue: 1
      },
      width: {
        defaultValue: "auto"
      },
      height: {
        defaultValue: "auto"
      },
      displayValue: {
        // Javascript which returns a string which will be displayed below the joystick.
        // The 'value' variable is available with the current value of the joystick,
        // which will be an object: { magnitude: 1, direction: 0 }.
        // ex: "return 'Aiming toward ' + value.direction + ' with speed ' + value.magnitude;"
      },
      // Use "property" OR "action" + "initialValue".
      // The joystick value is an object with 2 properties: magnitude and direction -- { magnitude: 1, direction: 0 }
      // Magnitude is always normalized to 0 to 1, and direction is in radians, 0 to 2*PI.
      property: {
        // If you use property binding, do not mix it with action scripts and initial values.
        conflictsWith: ["initialValue", "action"]
      },
      retainProperty: {
        // If property binding is used (so 'property' is defined), this flag decides whether
        // property should be retained during model reload / reset or not.
        defaultValue: true
      },
      action: {
        conflictsWith: ["property"]
      },
      initialValue: {
        conflictsWith: ["property"]
      },
      disabled: {
        defaultValue: false
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    colorIndicator: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      title: {
        defaultValue: ""
      },
      colorValue: {
        // Javascript which returns a valid css color -- #aa9933, rgb(), hsl(), etc.
        // The 'value' variable is available with the current value of the watched property.
        // ex: "return 'hsl('+value+',100%,50%)';"
        required: true
      },
      width: {
        defaultValue: "auto"
      },
      height: {
        defaultValue: "auto"
      },
      property: {
        required: true
      },
      retainProperty: {
        // This flag decides whether
        // property should be retained during model reload / reset or not.
        defaultValue: true
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    spectrometer: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      upperBound: {
        // Upper bound of frequency in eV.
        defaultValue: 15
      },
      lowerBound: {
        // Lower bound of frequency in eV.
        defaultValue: 2
      },
      ticks: {
        defaultValue: 10
      },
      clearOnModelLoad: {
        // Should spectrometer clear its output on model reload or when a new model is loaded?
        defaultValue: true
      },
      width: {
        defaultValue: "12em"
      },
      height: {
        defaultValue: "3em"
      },
      border: {
        // CSS border specification is accepted.
        defaultValue: "none"
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    table: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      title: {
        defaultValue: null
      },
      dataSet: {
        // Optional. When external data set is referenced, then properties listed in "conflictsWith"
        // array should be defined inside data set definition, not in table definition.
        conflictsWith: ["tableData", "clearOnModelReset", "streamDataFromModel"]
      },
      tableData: {
        conflictsWith: ["dataSet"]
      },
      clearOnModelReset: {
        conflictsWith: ["dataSet"]
      },
      streamDataFromModel: {
        conflictsWith: ["dataSet"]
      },
      addNewRows: {
        defaultValue: true
      },
      visibleRows: {
        defaultValue: 4
      },
      showBlankRow: {
        // If true, a new blank row will be always visible.
        defaultValue: false
      },
      indexColumn: {
        defaultValue: true
      },
      propertyColumns: {
        defaultValue: []
      },
      headerData: {
        defaultValue: []
      },
      width: {
        defaultValue: "auto"
      },
      height: {
        defaultValue: "100%"
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    graph: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      dataSet: {
        // Optional. When external data set is referenced, then properties listed in "conflictsWith"
        // array should be defined inside data set definition, not in table definition.
        conflictsWith: ["dataPoints", "clearOnModelReset", "streamDataFromModel"]
      },
      dataPoints: {
        conflictsWith: ["dataSet"]
      },
      clearOnModelReset: {
        conflictsWith: ["dataSet"]
      },
      streamDataFromModel: {
        conflictsWith: ["dataSet"]
      },
      resetAxesOnReset: {
        defaultValue: true
      },
      enableAutoScaleButton: {
        defaultValue: true
      },
      enableAxisScaling: {
        defaultValue: true
      },
      enableZooming: {
        defaultValue: true
      },
      autoScaleX: {
        defaultValue: true
      },
      autoScaleY: {
        defaultValue: true
      },
      enableSelectionButton: {
        defaultValue: false
      },
      clearSelectionOnLeavingSelectMode: {
        defaultValue: false
      },
      enableDrawButton: {
        defaultValue: false
      },
      drawProperty: {
        defaultValue: null
      },
      markAllDataPoints: {
        defaultValue: false
      },
      showRulersOnSelection: {
        defaultValue: false
      },
      fontScaleRelativeToParent: {
        defaultValue: true
      },
      hideAxisValues: {
        defaultValue: false
      },
      properties: {
        defaultValue: []
      },
      xProperty: {
        defaultValue: "displayTime"
      },
      title: {
        defaultValue: "Graph"
      },
      titlePosition: {
        // "center" or "left"
        defaultValue: "center"
      },
      buttonsStyle: {
        // "icons" or "text"
        defaultValue: "icons"
      },
      buttonsLayout: {
        // "vertical" or "horizontal"
        defaultValue: "vertical"
      },
      lineWidth: {
        defaultValue: 2.0
      },
      width: {
        defaultValue: "100%"
      },
      height: {
        defaultValue: "100%"
      },
      xlabel: {
        defaultValue: "auto"
      },
      xmin: {
        defaultValue: 0
      },
      xmax: {
        defaultValue: 20
      },
      ylabel: {
        defaultValue: "auto"
      },
      ymin: {
        defaultValue: 0
      },
      ymax: {
        defaultValue: 10
      },
      xTickCount: {
        defaultValue: 10
      },
      yTickCount: {
        defaultValue: 10
      },
      xscaleExponent: {
        defaultValue: 0.5
      },
      yscaleExponent: {
        defaultValue: 0.5
      },
      xFormatter: {
        defaultValue: ".2r"
      },
      yFormatter: {
        defaultValue: ".2r"
      },
      lines: {
        defaultValue: true
      },
      bars: {
        defaultValue: false
      },
      tooltip: {
        defaultValue: ""
      },
      dataColors: {
        defaultValue: [
          "#a00000",
          "#2ca000",
          "#2c00a0"
        ]
      },
      legendLabels: {
        defaultValue: []
      },
      legendVisible: {
        defaultValue: false
      },
      syncXAxis: {
        defaultValue: false
      },
      syncYAxis: {
        defaultValue: false
      },
      helpIcon: {
        defaultValue: false
      }
    },

    barGraph: {
      id: {
        required: true
      },
      type: {
        required: true
      },
      property: {
        required: true
      },
      secondProperty: {
        // Additional value displayed
        // using small triangle. E.g.
        // can be used to present
        // averaged value.
        conflictsWith: ["averagePeriod"]
      },
      min: {
        // Min value displayed.
        defaultValue: 0
      },
      max: {
        // Max value displayed.
        defaultValue: 10
      },
      title: {
        // Graph title.
        defaultValue: ""
      },
      titleOn: {
        // Title position, accepted values are:
        // "right", "top", "bottom"
        defaultValue: "right"
      },
      labels: {
        // Number of labels displayed on the left side of the graph.
        // This value is *only* a suggestion. The most clean
        // and human-readable values are used.
        // You can also specify value-label pairs, e.g.:
        // [
        //   {
        //     "value": 0,
        //     "label": "low"
        //   },
        //   {
        //     "value": 10,
        //     "label": "high"
        //   }
        // ]
        // Use 0 or null to disable labels completely.
        defaultValue: 5
      },
      units: {
        // Units displayed next to labels. Set it to 'true' to use units
        // automatically retrieved from property description. Set it to any
        // string to use custom unit symbol.
        defaultValue: false
      },
      gridLines: {
        // Number of grid lines displayed on the bar.
        // This value is *only* a suggestion, it's similar to 'ticks'.
        defaultValue: 10
      },
      labelFormat: {
        // Format of labels.
        // See the specification of this format:
        // https://github.com/mbostock/d3/wiki/Formatting#wiki-d3_format
        // or:
        // http://docs.python.org/release/3.1.3/library/string.html#formatspec
        defaultValue: "0.1f"
      },
      averagePeriod: {
        // Setting this property to some numeric value
        // enables displaying of the averaged property.
        // It's a shortcut which can be used instead
        // of a custom filtered output bound
        // to the "secondProperty".
        conflictsWith: ["secondProperty"]
      },
      barWidth: {
        // Widht of the bar graph, WITHOUT
        // labels, title and padding.
        defaultValue: "2em"
      },
      height: {
        // Height of the bar graph container,
        // including small padding.
        defaultValue: "100%"
      },
      barColor: {
        // Color of the main bar.
        defaultValue:  "#e23c34"
      },
      fillColor: {
        // Color of the area behind the bar.
        defaultValue: "#fff"
      },
      tooltip: {
        defaultValue: ""
      },
      helpIcon: {
        defaultValue: false
      }
    },

    helpTip: {
      component: {
        // Single component or array of components (bounding box of components will be used).
        // "" - help tip will be positioned in the center.
        defaultValue: ""
      },
      text: {
        defaultValue: ""
      },
      showcase: {
        // If false, help tip is not displayed when user enters showcase mode by clicking top-left "?" icon.
        // It can be displayed only by "?" icon provided by component.
        defaultValue: true
      }
    }
  };
});

/*global define: false */

define('common/resources-url',['require','lab.config'],function (require) {
  var config = require('lab.config');
  return function (resourcePath) {
    return config.rootUrl + "/resources/" + resourcePath;
  };
});

/*global define, $ */

define('common/controllers/language-select',['require','common/resources-url'],function (require) {
  var resourcesUrl = require('common/resources-url');

  function languageSelect(selector, interactiveController) {
    var metadata = interactiveController.interactive.i18nMetadata;
    if (!metadata) return;

    var metadataDownloaded = $.get(metadata).done(function(results) {
      if (typeof results === 'string') results = JSON.parse(results);
      setupContextMenu(selector, results, interactiveController.interactive.lang,
                       interactiveController);
    });

    var interactiveRendered = new $.Deferred();
    interactiveController.on('interactiveRendered.i18nHelper', function() {
      interactiveRendered.resolve();
    });

    $.when(metadataDownloaded, interactiveRendered).done(function () {
      setupLangIcon(selector, interactiveController.interactive.lang);
    });
  }

  // Private functions used by i18nHelper.

  function code2flag(countryCode) {
    var arr = countryCode.split('-');
    // Handle special cases like "en-US", "en-GB" etc.
    return resourcesUrl('flags/' + arr[arr.length - 1].toLowerCase() + '.png');
  }

  function setupLangIcon(selector, currentLang) {
    var $icon = $(selector);
    $icon.addClass('lang-icon');
    $icon.css('background-image', 'url("' + code2flag(currentLang) + '")');
  }

  function setupContextMenu(selector, i18nMetadata, currentLang, interactiveController) {
    var items = {};
    Object.keys(i18nMetadata).forEach(function (key) {
      if (key === currentLang) return;
      items[key] = {
        name: key,
        className: 'lang-' + key
      };
    });
    if (Object.keys(items).length === 0) return;
    // When 'trigger' is set to 'none' and menu is opened manually using .contextMenu() call,
    // it causes that menu positioning doesn't use mouse pointer coordinates. It's the simplest way
    // to force a menu to always show below the flag.
    $(selector).on('click', function () {
      $(selector).contextMenu(); // ! open manu manually
    });
    $.contextMenu('destroy', selector);
    $.contextMenu({
      selector: selector,
      appendTo: '.lab-responsive-content',
      className: 'lang-menu',
      trigger: 'none', // !
      zIndex: 1000, // avoid conflict with layout containers
      determinePosition: function($menu) {
        // position to the lower left of the trigger element
        // .position() is provided as a jQuery UI utility
        // (...and it won't work on hidden elements)
        $menu.css('display', 'block').position({
            my: "right top",
            at: "right bottom",
            of: this,
            offset: "0 5",
            collision: "fit"
        }).css('display', 'none');
      },
      callback: function(key) {
        interactiveController.requestInteractiveAt(i18nMetadata[key]);
      },
      items: items
    });
    Object.keys(items).forEach(function (key) {
      $('.context-menu-item.lang-' + key).css('background-image', 'url("' + code2flag(key) + '")');
    });
  }

  return languageSelect;
});

define('common/listening-pool',[],function () {

  /**
   * isD3Listner : is a given listener a D3 listener?
   * @param {listener} the listener to check for D3
   */
  var isD3Listner = function(listener) {
    // D3 events don't use "off", they issue another 'on' with same name
    // see: https://github.com/mbostock/d3/wiki/Internals#wiki-dispatch_on
    if (typeof listener.off !== 'function') {
      return true;
    }
    return false;
  };

  /**
   * ListeningPool:  A simple helper to keep track of the events you
   * are listening too.
   *
   * @constructor
   *
   * @param {Namespace} our event namespace. Required for D3 events,
   * handy for JQuery event types.
   */
  function ListeningPool(namespace) {
    this._nameSpace           = namespace;
    this.registeredListeners  = [];

    this._nameSpaced          = function (eventName) {
      return eventName + "." + this._nameSpace;
    };
  }

  /**
   * listen: register a new listener on a speaker.
   *
   * @param {speaker}  object we are listening too.
   * @param {eventName} the event type we are listening for
   * @param {func} callback to invoke when event is fired.
   */
  ListeningPool.prototype.listen = function (speaker, eventName, func) {
    var eventKey = this._nameSpaced(eventName);
    var listeningRecord = {
        speaker: speaker,
        eventName: eventKey
    };
    speaker.on(eventKey, func);
    this.registeredListeners.push(listeningRecord);
  };

  /**
   * remove : remove a listener from the registeredListeners
   * @param {listener} the listener to remove
   */
  ListeningPool.prototype.remove = function(listener) {
    if (isD3Listner(listener)) {
      listener.speaker.on(listener.eventName, null); // How D3 removes listeners...
    }
    else {
      listener.speaker.off(listener.eventName); // How JQuery removes listeners..
    }
  };

  /**
   * removeAll : remove ourself as a listener from all our speakers.
   * (We could simply use JQuery off(/namespace/) if we stick to JQ events)
   */
  ListeningPool.prototype.removeAll = function () {
    while(this.registeredListeners.length > 0) {
      this.remove(this.registeredListeners.pop());
    }
  };

  return ListeningPool;
});

/*global define: false, d3: false */
/**
 * This module provides event dispatch based on d3.dispatch:
 * https://github.com/mbostock/d3/wiki/Internals#wiki-d3_dispatch
 *
 * The main improvement over raw d3.dispatch is that this wrapper provides
 * event batching. You can start batch mode (.startBatch()) and while it is
 * active events won't be dispatched immediately. They will be dispatched
 * at the end of batch mode (.endBatch()) or when you call .flush() method.
 *
 * Note that there is one *significant limitation*: arguments passed during
 * event dispatching will be lost! All events will be merged into single
 * event without any argument. Please keep this in mind while using this module.
 *
 * e.g.
 *   dispatch.on("someEvent", function(arg) { console.log(arg); });
 *   dispatch.someEvent(123);     // console output: 123
 *   dispatch.someEvent("test");  // console output: "test"
 * However...
 *   dispatch.startBatch();
 *   dispatch.someEvent(123);
 *   dispatch.someEvent("test");
 *   dispatch.endBatch();         // console output: undefined (!)
 *
 * Rest of the interface is exactly the same like in d3.dispatch (.on()).
 * Under the hood delegation to d3.dispatch instance is used.
 */
define('common/dispatch-support',[],function() {

  // Converts arguments object to regular array.
  function argsToArray(args) {
    return [].slice.call(args);
  }

  return function DispatchSupport() {
    var api,
        d3dispatch,
        types,

        batchMode = false,
        suppressedEvents = d3.set();

    function init(newTypes) {
      var i, len;

      types = newTypes;

      d3dispatch = d3.dispatch.apply(null, types);

      // Provide wrapper around typical calls like dispatch.someEvent().
      for (i = 0, len = types.length; i < len; i++) {
        api[types[i]] = dispatchEvent(types[i]);
      }
    }

    function dispatchEvent(name) {
      return function () {
        if (!batchMode) {
          d3dispatch[name].apply(d3dispatch, arguments);
        } else {
          suppressedEvents.add(name);
        }
      };
    }

    function delegate(funcName) {
      return function () {
        d3dispatch[funcName].apply(d3dispatch, arguments);
      };
    }

    // Public API.
    api = {
      // Copy d3.dispatch API:

      /**
       * Adds or removes an event listener for the specified type. Please see:
       * https://github.com/mbostock/d3/wiki/Internals#wiki-dispatch_on
       */
      on: delegate("on"),

      // New API specific for Lab DispatchSupport:

      mixInto: function(target) {
        target.on = api.on;
        target.suppressEvents = api.suppressEvents;
      },

      /**
       * Adds new event types. Old event types are still supported, but
       * all previously registered listeners will be removed!
       *
       * e.g. dispatch.addEventTypes("newEvent", "anotherEvent")
       */
      addEventTypes: function () {
        if (arguments.length) {
          init(types.concat(argsToArray(arguments)));
        }
      },

      /**
       * Starts batch mode. Events won't be dispatched immediately after call.
       * They will be merged into single event and dispatched when .flush()
       * or .endBatch() is called.
       */
      startBatch: function () {
        batchMode = true;
      },

      /**
       * Ends batch mode and dispatches suppressed events.
       */
      endBatch: function () {
        batchMode = false;
        api.flush();
      },

      /**
       * Dispatches suppressed events.
       * @return {[type]} [description]
       */
      flush: function () {
        suppressedEvents.forEach(function (eventName) {
          d3dispatch[eventName]();
        });
        // Reset suppressed events.
        suppressedEvents = d3.set();
      },

      /**
       * Allows to execute some action without dispatching any events.
       * @param {function} action
       */
      suppressEvents: function(action) {
        batchMode = true;
        action();
        batchMode = false;
        // Reset suppressed events without dispatching them.
        suppressedEvents = d3.set();
      }
    };

    init(argsToArray(arguments));

    return api;
  };
});

define('common/controllers/data-set',['common/controllers/interactive-metadata','common/validator','common/listening-pool','common/dispatch-support'],function () {
  var metadata        = require('common/controllers/interactive-metadata');
  var validator       = require('common/validator');
  var ListeningPool   = require('common/listening-pool');
  var DispatchSupport = require('common/dispatch-support');
  var dataSetCount    = 0;

  /**
   * DataSet: Manage Collections of data for tables, graphs, others.
   *
   * @constructor
   *
   * @param {object}                 component              The json definition for our dataset.
   * @param {interactivesController} interactivesController InteractivesController instance.
   * @param {boolean}                private                If true, data set will register itself
   *                                                        as 'private' data set in interactives
   *                                                        controller.
   */
  function DataSet(component, interactivesController, private) {
    this.interactivesController = interactivesController;
    this._model                 = interactivesController.getModel();
    this.namespace              = "dataSet" + (++dataSetCount);
    this.component              = validator.validateCompleteness(metadata.dataSet, component);
    this.name                   = this.component.name;
    this.properties             = this.component.properties || [];
    this.streamDataFromModel    = this.component.streamDataFromModel;
    this.clearOnModelReset      = this.component.clearOnModelReset;
    // Set initial data only if there is real data there. Otherwise set null for convenience.
    this.initialData            = this.component.initialData ?
                                  $.extend(true, {}, this.component.initialData) : null;
    this._data                  = {};
    // Keep clear distinction between model properties and other properties (e.g. they can be
    // filled by the user). Data streaming streams only model properties.
    this._modelProperties       = [];
    this._listeningPool         = new ListeningPool(this.namespace);
    this._dispatch              = new DispatchSupport();

    for (var key in DataSet.Events) {
      this._dispatch.addEventTypes(DataSet.Events[key]);
    }
    this._dispatch.mixInto(this);

    // This will initialize _data in a right way (e.g. copy initial data).
    this.resetData();

    // Finally register itself in interactives controller (e.g. it's necessary to ensure that
    // modelLoadedCallback will be called).
    this.interactivesController.addDataSet(this, private);
  }

  DataSet.Events = {
    SAMPLE_ADDED:      "sampleAdded",
    SAMPLE_CHANGED:    "sampleChanged",
    SAMPLE_REMOVED:    "sampleRemoved",

    DATA_TRUNCATED:    "dataTruncated",
    DATA_RESET:        "dataReset",

    SELECTION_CHANGED: "selectionChanged",

    LABELS_CHANGED:    "labelsChanged"
  };

  /******************************************************************
    "Private" methods, not intended for use by outside objects.
  *******************************************************************/

  DataSet.prototype._setupEmptyData = function () {
    var context = this;
    this.properties.forEach(function (prop) {
      context._data[prop] = [];
    });
  };

  /**
    Check that we haven't invalidated future datapoints.
  */
  DataSet.prototype._inNewModelTerritory = function () {
    return (this._model.stepCounter() < this.maxLength(this._modelProperties));
  };

  /**
    register model listeners
  */
  DataSet.prototype._addListeners = function() {
    var listeningPool  = this._listeningPool;
    var model          = this.interactivesController.getModel();
    var context        = this;

    var positionChanged = function() {
      context._trigger(DataSet.Events.SELECTION_CHANGED, model.stepCounter());
    };

    listeningPool.removeAll(); // remove previous listeners.

    if (this.streamDataFromModel) {
      listeningPool.listen(model, 'tick', function () {
        context.appendDataPoint();
        positionChanged();
      });

      listeningPool.listen(model, 'play', function() {
        if (context._inNewModelTerritory()) {
          context.removeModelDataAfterStepPointer();
        }
      });

      listeningPool.listen(model, 'stepBack',    positionChanged);
      listeningPool.listen(model, 'stepForward', positionChanged);
      listeningPool.listen(model, 'seek',        positionChanged);

      listeningPool.listen(model, 'invalidation', function() {
        context.removeModelDataAfterStepPointer();
      });
    }

    listeningPool.listen(model, 'reset', function() {
      if (context.clearOnModelReset) {
        context.resetData();
      }
      if (context.streamDataFromModel) {
        context.appendDataPoint();
      }
    });

    this.properties.forEach(function (prop) {
      context._model.addPropertyDescriptionObserver(prop, function() {
        context._trigger(DataSet.Events.LABELS_CHANGED, context.getLabels());
      });
    });
  };

  /**
    Law of demeter workaround ;)
  */
  DataSet.prototype._getModelProperty = function (propName) {
    return this._model.get(propName);
  };

  /**
    Trigger a custom event for listeners.
    @param {name} event name we are triggering
    @param {data} extra data for the event.
  */
  DataSet.prototype._trigger = function (name, data) {
    this._dispatch[name]({'data': data});
  };

  DataSet.prototype._getPropertyLabel = function(prop) {
    if (!this._model.hasProperty(prop)) return "";
    var description = this._model.getPropertyDescription(prop);
    return description.getLabel() + " (" + description.getUnitAbbreviation() + ")";
  };

  DataSet.prototype._resetProperty = function(prop, values) {
    var newValue = [];
    if (values && values[prop]) {
      newValue = values[prop];
    } else if (this.initialData[prop]) {
      newValue = this.initialData[prop];
    }
    this._data[prop] = newValue.slice(0); // always use a copy
  };


  /******************************************************************
    "Public" methods, should have associated unit tests.
  *******************************************************************/

  DataSet.prototype.getData = function() {
    return this._data;
  };

  DataSet.prototype.maxLength = function(props) {
    var maxLength = -Infinity;
    var context = this;
    props.forEach(function (prop) {
      if (maxLength < context._data[prop].length) maxLength = context._data[prop].length;
    });
    return maxLength;
  };

  DataSet.prototype.minLength = function(props) {
    var minLength = Infinity;
    var context = this;
    props.forEach(function (prop) {
      if (minLength > context._data[prop].length) minLength = context._data[prop].length;
    });
    return minLength;
  };

  /**
   Returns index of a data point if all provided values are matching.
   For example if data set has following properties and values:
   {
     x: [0, 1, 2, 3],
     y: [0, 10, 20, 30]
   }
   then:
   dataset.dataPointIndex({x: 2, y: 20}); // returns: 2
   dataset.dataPointIndex({x: 2});        // returns: 2
   dataset.dataPointIndex({x: 2, y: 99}); // returns: -1 (not found)
   */
  DataSet.prototype.dataPointIndex = function (values) {
    var props = Object.keys(values);
    var valuesLength = this.minLength(props);
    var propsLength = props.length;
    var allValuesMatching;
    var prop;

    for (var index = 0; index < valuesLength; index++) {
      allValuesMatching = true;
      for (var j = 0; j < propsLength; j++) {
        prop = props[j];
        if (this._data[prop][index] !== values[prop]) {
          allValuesMatching = false;
          break;
        }
      }
      if (allValuesMatching) {
        return index;
      }
    }
    return -1;
  };

  /**
    Resets data sat to its initial data. When initial data is not provided, clears data
    set (in such case this function behaves exactly like .clearData()).
  */
  DataSet.prototype.resetData = function () {
    this._setupEmptyData();
    if (this.initialData) {
      $.extend(true, this._data, this.initialData);
    }
    this._trigger(DataSet.Events.DATA_RESET, this._data);
  };

  /**
    Clears completely data set.
   */
  DataSet.prototype.clearData = function () {
    this._setupEmptyData();
    this._trigger(DataSet.Events.DATA_RESET, this._data);
  };

  DataSet.prototype.resetProperties = function (props) {
    var i;
    for (i = 0; i < props.length; i++) {
      this._resetProperty(props[i]);
    }
    this._trigger(DataSet.Events.DATA_RESET, this._data);
  };

  DataSet.prototype.appendDataPoint = function (props, values) {
    if (!props) {
      props = this._modelProperties;
    }
    var dataPoint = {};
    var context = this;
    props.forEach(function (prop) {
      var val = values && values[prop] !== undefined ? values[prop] : context._getModelProperty(prop);
      if (val === undefined) return;
      dataPoint[prop] = val;
      context._data[prop].push(val);
    });

    this._trigger(DataSet.Events.SAMPLE_ADDED, dataPoint);
  };

  DataSet.prototype.removeDataPoint = function (props, index) {
    var context = this;
    props.forEach(function (prop) {
      context._data[prop][index] = null;
    });
    this._trigger(DataSet.Events.SAMPLE_REMOVED, {props: props, index: index});
  };


  /**
    Removes all data that correspond to steps following the current step pointer. This is used when
    a change is made that invalidates the future data.
  */
  DataSet.prototype.removeModelDataAfterStepPointer = function () {
    var newLength = this._model.stepCounter();
    var context = this;

    if (newLength < 0) {
      newLength = 0;
    }

    this._modelProperties.forEach(function (prop) {
      if (context._data[prop].length > newLength) {
        context._data[prop].length = newLength;
      }
    });

    this._trigger(DataSet.Events.DATA_TRUNCATED, this._data);

    // Note that code above also removes point equal to step pointer! It's intentional.
    // Now we append the last point again to be sure that it contains updated values of model
    // properties (as invalidation in most cases is related to change of some model property).
    context.appendDataPoint();
  };

  DataSet.prototype.editDataPoint = function (index, property, newValue) {
    this._data[property][index] = newValue;

    var context = this;
    var dataPoint = {};
    this.properties.forEach(function (prop) {
      dataPoint[prop] = context._data[prop][index];
    });

    this._trigger(DataSet.Events.SAMPLE_CHANGED, {index:     index,
                                                  property:  property,
                                                  value:     newValue,
                                                  dataPoint: dataPoint});
  };

  DataSet.prototype.getPropertyValue = function (index, property) {
    return this._data[property][index];
  };

  /**
    Return properties labels (array).
   */
  DataSet.prototype.getLabels = function() {
    var res = {};
    var context = this;
    this.properties.forEach(function (prop) {
      res[prop] = context._getPropertyLabel(prop);
    });
    return res;
  };

  /**
    Called when the model has loaded. Setup listeners. Clear Data.
  */
  DataSet.prototype.modelLoadedCallback = function() {
    this._model = this.interactivesController.getModel();
    this._addListeners();
    // Keep list of properties that are defined in model. Only these properties will be streamed.
    this._modelProperties = [];
    var context = this;
    this.properties.forEach(function (prop) {
      if (context._model.hasProperty(prop)) {
        context._modelProperties.push(prop);
      }
    });
    if (this.clearOnModelReset) {
      this.resetData();
    }
    if (this.streamDataFromModel) {
      this.appendDataPoint();
    }
  };

  DataSet.prototype.serialize = function () {
    // Start with the initial component definition.
    var result = $.extend(true, {}, this.component);
    // Save current data as initial data.
    result.initialData = this.serializeData();
    return result;
  };

  DataSet.prototype.serializeData = function () {
    if (this.component.serializableProperties === "none") return {};
    var props = this.component.serializableProperties === "all" ?
                this.properties : this.component.serializableProperties;
    var result = {};
    var context = this;
    props.forEach(function (prop) {
      result[prop] = $.extend(true, [], context._data[prop]);
    });
    return result;
  };

  // Handle events which are generated by a different dataset.
  // This will keep this data set in sync with the other one.
  DataSet.prototype.handleExternalEvent = function(evtName, data) {
    switch(evtName) {
      case DataSet.Events.SAMPLE_ADDED:
        this.appendDataPoint(Object.keys(data), data);
        break;
      case DataSet.Events.SAMPLE_CHANGED:
        this.editDataPoint(data['index'], data['property'], data['newValue']);
        break;
      case DataSet.Events.SAMPLE_REMOVED:
        this.removeDataPoint(data['props'], data['index']);
        break;

      case DataSet.Events.DATA_TRUNCATED:
        // TODO
        break;
      case DataSet.Events.DATA_RESET:
        var context = this;
        Object.keys(data).forEach(function(prop) {
          context._resetProperty(prop, data);
        });
        this._trigger(DataSet.Events.DATA_RESET, this._data);
        break;

      case DataSet.Events.SELECTION_CHANGED:
        // TODO
        break;

      case DataSet.Events.LABELS_CHANGED:
        // TODO
        break;
    }
  };

  return DataSet;
});

//     Backbone.js 1.1.0

//     (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc.
//     (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
//     Backbone may be freely distributed under the MIT license.
//     For all details and documentation:
//     http://backbonejs.org

(function(){

  // Initial Setup
  // -------------

  // Save a reference to the global object (`window` in the browser, `exports`
  // on the server).
  var root = this;

  // Save the previous value of the `Backbone` variable, so that it can be
  // restored later on, if `noConflict` is used.
  var previousBackbone = root.Backbone;

  // Create local references to array methods we'll want to use later.
  var array = [];
  var push = array.push;
  var slice = array.slice;
  var splice = array.splice;

  // The top-level namespace. All public Backbone classes and modules will
  // be attached to this. Exported for both the browser and the server.
  var Backbone;
  if (typeof exports !== 'undefined') {
    Backbone = exports;
  } else {
    Backbone = root.Backbone = {};
  }

  // Current version of the library. Keep in sync with `package.json`.
  Backbone.VERSION = '1.1.0';

  // Require Underscore, if we're on the server, and it's not already present.
  var _ = root._;
  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');

  // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
  // the `$` variable.
  Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;

  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
  // to its previous owner. Returns a reference to this Backbone object.
  Backbone.noConflict = function() {
    root.Backbone = previousBackbone;
    return this;
  };

  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
  // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
  // set a `X-Http-Method-Override` header.
  Backbone.emulateHTTP = false;

  // Turn on `emulateJSON` to support legacy servers that can't deal with direct
  // `application/json` requests ... will encode the body as
  // `application/x-www-form-urlencoded` instead and will send the model in a
  // form param named `model`.
  Backbone.emulateJSON = false;

  // Backbone.Events
  // ---------------

  // A module that can be mixed in to *any object* in order to provide it with
  // custom events. You may bind with `on` or remove with `off` callback
  // functions to an event; `trigger`-ing an event fires all callbacks in
  // succession.
  //
  //     var object = {};
  //     _.extend(object, Backbone.Events);
  //     object.on('expand', function(){ alert('expanded'); });
  //     object.trigger('expand');
  //
  var Events = Backbone.Events = {

    // Bind an event to a `callback` function. Passing `"all"` will bind
    // the callback to all events fired.
    on: function(name, callback, context) {
      if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
      this._events || (this._events = {});
      var events = this._events[name] || (this._events[name] = []);
      events.push({callback: callback, context: context, ctx: context || this});
      return this;
    },

    // Bind an event to only be triggered a single time. After the first time
    // the callback is invoked, it will be removed.
    once: function(name, callback, context) {
      if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
      var self = this;
      var once = _.once(function() {
        self.off(name, once);
        callback.apply(this, arguments);
      });
      once._callback = callback;
      return this.on(name, once, context);
    },

    // Remove one or many callbacks. If `context` is null, removes all
    // callbacks with that function. If `callback` is null, removes all
    // callbacks for the event. If `name` is null, removes all bound
    // callbacks for all events.
    off: function(name, callback, context) {
      var retain, ev, events, names, i, l, j, k;
      if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
      if (!name && !callback && !context) {
        this._events = {};
        return this;
      }
      names = name ? [name] : _.keys(this._events);
      for (i = 0, l = names.length; i < l; i++) {
        name = names[i];
        if (events = this._events[name]) {
          this._events[name] = retain = [];
          if (callback || context) {
            for (j = 0, k = events.length; j < k; j++) {
              ev = events[j];
              if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
                  (context && context !== ev.context)) {
                retain.push(ev);
              }
            }
          }
          if (!retain.length) delete this._events[name];
        }
      }

      return this;
    },

    // Trigger one or many events, firing all bound callbacks. Callbacks are
    // passed the same arguments as `trigger` is, apart from the event name
    // (unless you're listening on `"all"`, which will cause your callback to
    // receive the true name of the event as the first argument).
    trigger: function(name) {
      if (!this._events) return this;
      var args = slice.call(arguments, 1);
      if (!eventsApi(this, 'trigger', name, args)) return this;
      var events = this._events[name];
      var allEvents = this._events.all;
      if (events) triggerEvents(events, args);
      if (allEvents) triggerEvents(allEvents, arguments);
      return this;
    },

    // Tell this object to stop listening to either specific events ... or
    // to every object it's currently listening to.
    stopListening: function(obj, name, callback) {
      var listeningTo = this._listeningTo;
      if (!listeningTo) return this;
      var remove = !name && !callback;
      if (!callback && typeof name === 'object') callback = this;
      if (obj) (listeningTo = {})[obj._listenId] = obj;
      for (var id in listeningTo) {
        obj = listeningTo[id];
        obj.off(name, callback, this);
        if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
      }
      return this;
    }

  };

  // Regular expression used to split event strings.
  var eventSplitter = /\s+/;

  // Implement fancy features of the Events API such as multiple event
  // names `"change blur"` and jQuery-style event maps `{change: action}`
  // in terms of the existing API.
  var eventsApi = function(obj, action, name, rest) {
    if (!name) return true;

    // Handle event maps.
    if (typeof name === 'object') {
      for (var key in name) {
        obj[action].apply(obj, [key, name[key]].concat(rest));
      }
      return false;
    }

    // Handle space separated event names.
    if (eventSplitter.test(name)) {
      var names = name.split(eventSplitter);
      for (var i = 0, l = names.length; i < l; i++) {
        obj[action].apply(obj, [names[i]].concat(rest));
      }
      return false;
    }

    return true;
  };

  // A difficult-to-believe, but optimized internal dispatch function for
  // triggering events. Tries to keep the usual cases speedy (most internal
  // Backbone events have 3 arguments).
  var triggerEvents = function(events, args) {
    var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
    switch (args.length) {
      case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
      case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
      case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
      case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
      default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
    }
  };

  var listenMethods = {listenTo: 'on', listenToOnce: 'once'};

  // Inversion-of-control versions of `on` and `once`. Tell *this* object to
  // listen to an event in another object ... keeping track of what it's
  // listening to.
  _.each(listenMethods, function(implementation, method) {
    Events[method] = function(obj, name, callback) {
      var listeningTo = this._listeningTo || (this._listeningTo = {});
      var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
      listeningTo[id] = obj;
      if (!callback && typeof name === 'object') callback = this;
      obj[implementation](name, callback, this);
      return this;
    };
  });

  // Aliases for backwards compatibility.
  Events.bind   = Events.on;
  Events.unbind = Events.off;

  // Allow the `Backbone` object to serve as a global event bus, for folks who
  // want global "pubsub" in a convenient place.
  _.extend(Backbone, Events);

  // Backbone.Model
  // --------------

  // Backbone **Models** are the basic data object in the framework --
  // frequently representing a row in a table in a database on your server.
  // A discrete chunk of data and a bunch of useful, related methods for
  // performing computations and transformations on that data.

  // Create a new model with the specified attributes. A client id (`cid`)
  // is automatically generated and assigned for you.
  var Model = Backbone.Model = function(attributes, options) {
    var attrs = attributes || {};
    options || (options = {});
    this.cid = _.uniqueId('c');
    this.attributes = {};
    if (options.collection) this.collection = options.collection;
    if (options.parse) attrs = this.parse(attrs, options) || {};
    attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
    this.set(attrs, options);
    this.changed = {};
    this.initialize.apply(this, arguments);
  };

  // Attach all inheritable methods to the Model prototype.
  _.extend(Model.prototype, Events, {

    // A hash of attributes whose current and previous value differ.
    changed: null,

    // The value returned during the last failed validation.
    validationError: null,

    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
    // CouchDB users may want to set this to `"_id"`.
    idAttribute: 'id',

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function(){},

    // Return a copy of the model's `attributes` object.
    toJSON: function(options) {
      return _.clone(this.attributes);
    },

    // Proxy `Backbone.sync` by default -- but override this if you need
    // custom syncing semantics for *this* particular model.
    sync: function() {
      return Backbone.sync.apply(this, arguments);
    },

    // Get the value of an attribute.
    get: function(attr) {
      return this.attributes[attr];
    },

    // Get the HTML-escaped value of an attribute.
    escape: function(attr) {
      return _.escape(this.get(attr));
    },

    // Returns `true` if the attribute contains a value that is not null
    // or undefined.
    has: function(attr) {
      return this.get(attr) != null;
    },

    // Set a hash of model attributes on the object, firing `"change"`. This is
    // the core primitive operation of a model, updating the data and notifying
    // anyone who needs to know about the change in state. The heart of the beast.
    set: function(key, val, options) {
      var attr, attrs, unset, changes, silent, changing, prev, current;
      if (key == null) return this;

      // Handle both `"key", value` and `{key: value}` -style arguments.
      if (typeof key === 'object') {
        attrs = key;
        options = val;
      } else {
        (attrs = {})[key] = val;
      }

      options || (options = {});

      // Run validation.
      if (!this._validate(attrs, options)) return false;

      // Extract attributes and options.
      unset           = options.unset;
      silent          = options.silent;
      changes         = [];
      changing        = this._changing;
      this._changing  = true;

      if (!changing) {
        this._previousAttributes = _.clone(this.attributes);
        this.changed = {};
      }
      current = this.attributes, prev = this._previousAttributes;

      // Check for changes of `id`.
      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];

      // For each `set` attribute, update or delete the current value.
      for (attr in attrs) {
        val = attrs[attr];
        if (!_.isEqual(current[attr], val)) changes.push(attr);
        if (!_.isEqual(prev[attr], val)) {
          this.changed[attr] = val;
        } else {
          delete this.changed[attr];
        }
        unset ? delete current[attr] : current[attr] = val;
      }

      // Trigger all relevant attribute changes.
      if (!silent) {
        if (changes.length) this._pending = true;
        for (var i = 0, l = changes.length; i < l; i++) {
          this.trigger('change:' + changes[i], this, current[changes[i]], options);
        }
      }

      // You might be wondering why there's a `while` loop here. Changes can
      // be recursively nested within `"change"` events.
      if (changing) return this;
      if (!silent) {
        while (this._pending) {
          this._pending = false;
          this.trigger('change', this, options);
        }
      }
      this._pending = false;
      this._changing = false;
      return this;
    },

    // Remove an attribute from the model, firing `"change"`. `unset` is a noop
    // if the attribute doesn't exist.
    unset: function(attr, options) {
      return this.set(attr, void 0, _.extend({}, options, {unset: true}));
    },

    // Clear all attributes on the model, firing `"change"`.
    clear: function(options) {
      var attrs = {};
      for (var key in this.attributes) attrs[key] = void 0;
      return this.set(attrs, _.extend({}, options, {unset: true}));
    },

    // Determine if the model has changed since the last `"change"` event.
    // If you specify an attribute name, determine if that attribute has changed.
    hasChanged: function(attr) {
      if (attr == null) return !_.isEmpty(this.changed);
      return _.has(this.changed, attr);
    },

    // Return an object containing all the attributes that have changed, or
    // false if there are no changed attributes. Useful for determining what
    // parts of a view need to be updated and/or what attributes need to be
    // persisted to the server. Unset attributes will be set to undefined.
    // You can also pass an attributes object to diff against the model,
    // determining if there *would be* a change.
    changedAttributes: function(diff) {
      if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
      var val, changed = false;
      var old = this._changing ? this._previousAttributes : this.attributes;
      for (var attr in diff) {
        if (_.isEqual(old[attr], (val = diff[attr]))) continue;
        (changed || (changed = {}))[attr] = val;
      }
      return changed;
    },

    // Get the previous value of an attribute, recorded at the time the last
    // `"change"` event was fired.
    previous: function(attr) {
      if (attr == null || !this._previousAttributes) return null;
      return this._previousAttributes[attr];
    },

    // Get all of the attributes of the model at the time of the previous
    // `"change"` event.
    previousAttributes: function() {
      return _.clone(this._previousAttributes);
    },

    // Fetch the model from the server. If the server's representation of the
    // model differs from its current attributes, they will be overridden,
    // triggering a `"change"` event.
    fetch: function(options) {
      options = options ? _.clone(options) : {};
      if (options.parse === void 0) options.parse = true;
      var model = this;
      var success = options.success;
      options.success = function(resp) {
        if (!model.set(model.parse(resp, options), options)) return false;
        if (success) success(model, resp, options);
        model.trigger('sync', model, resp, options);
      };
      wrapError(this, options);
      return this.sync('read', this, options);
    },

    // Set a hash of model attributes, and sync the model to the server.
    // If the server returns an attributes hash that differs, the model's
    // state will be `set` again.
    save: function(key, val, options) {
      var attrs, method, xhr, attributes = this.attributes;

      // Handle both `"key", value` and `{key: value}` -style arguments.
      if (key == null || typeof key === 'object') {
        attrs = key;
        options = val;
      } else {
        (attrs = {})[key] = val;
      }

      options = _.extend({validate: true}, options);

      // If we're not waiting and attributes exist, save acts as
      // `set(attr).save(null, opts)` with validation. Otherwise, check if
      // the model will be valid when the attributes, if any, are set.
      if (attrs && !options.wait) {
        if (!this.set(attrs, options)) return false;
      } else {
        if (!this._validate(attrs, options)) return false;
      }

      // Set temporary attributes if `{wait: true}`.
      if (attrs && options.wait) {
        this.attributes = _.extend({}, attributes, attrs);
      }

      // After a successful server-side save, the client is (optionally)
      // updated with the server-side state.
      if (options.parse === void 0) options.parse = true;
      var model = this;
      var success = options.success;
      options.success = function(resp) {
        // Ensure attributes are restored during synchronous saves.
        model.attributes = attributes;
        var serverAttrs = model.parse(resp, options);
        if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
        if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
          return false;
        }
        if (success) success(model, resp, options);
        model.trigger('sync', model, resp, options);
      };
      wrapError(this, options);

      method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
      if (method === 'patch') options.attrs = attrs;
      xhr = this.sync(method, this, options);

      // Restore attributes.
      if (attrs && options.wait) this.attributes = attributes;

      return xhr;
    },

    // Destroy this model on the server if it was already persisted.
    // Optimistically removes the model from its collection, if it has one.
    // If `wait: true` is passed, waits for the server to respond before removal.
    destroy: function(options) {
      options = options ? _.clone(options) : {};
      var model = this;
      var success = options.success;

      var destroy = function() {
        model.trigger('destroy', model, model.collection, options);
      };

      options.success = function(resp) {
        if (options.wait || model.isNew()) destroy();
        if (success) success(model, resp, options);
        if (!model.isNew()) model.trigger('sync', model, resp, options);
      };

      if (this.isNew()) {
        options.success();
        return false;
      }
      wrapError(this, options);

      var xhr = this.sync('delete', this, options);
      if (!options.wait) destroy();
      return xhr;
    },

    // Default URL for the model's representation on the server -- if you're
    // using Backbone's restful methods, override this to change the endpoint
    // that will be called.
    url: function() {
      var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
      if (this.isNew()) return base;
      return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
    },

    // **parse** converts a response into the hash of attributes to be `set` on
    // the model. The default implementation is just to pass the response along.
    parse: function(resp, options) {
      return resp;
    },

    // Create a new model with identical attributes to this one.
    clone: function() {
      return new this.constructor(this.attributes);
    },

    // A model is new if it has never been saved to the server, and lacks an id.
    isNew: function() {
      return this.id == null;
    },

    // Check if the model is currently in a valid state.
    isValid: function(options) {
      return this._validate({}, _.extend(options || {}, { validate: true }));
    },

    // Run validation against the next complete set of model attributes,
    // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
    _validate: function(attrs, options) {
      if (!options.validate || !this.validate) return true;
      attrs = _.extend({}, this.attributes, attrs);
      var error = this.validationError = this.validate(attrs, options) || null;
      if (!error) return true;
      this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
      return false;
    }

  });

  // Underscore methods that we want to implement on the Model.
  var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];

  // Mix in each Underscore method as a proxy to `Model#attributes`.
  _.each(modelMethods, function(method) {
    Model.prototype[method] = function() {
      var args = slice.call(arguments);
      args.unshift(this.attributes);
      return _[method].apply(_, args);
    };
  });

  // Backbone.Collection
  // -------------------

  // If models tend to represent a single row of data, a Backbone Collection is
  // more analagous to a table full of data ... or a small slice or page of that
  // table, or a collection of rows that belong together for a particular reason
  // -- all of the messages in this particular folder, all of the documents
  // belonging to this particular author, and so on. Collections maintain
  // indexes of their models, both in order, and for lookup by `id`.

  // Create a new **Collection**, perhaps to contain a specific type of `model`.
  // If a `comparator` is specified, the Collection will maintain
  // its models in sort order, as they're added and removed.
  var Collection = Backbone.Collection = function(models, options) {
    options || (options = {});
    if (options.model) this.model = options.model;
    if (options.comparator !== void 0) this.comparator = options.comparator;
    this._reset();
    this.initialize.apply(this, arguments);
    if (models) this.reset(models, _.extend({silent: true}, options));
  };

  // Default options for `Collection#set`.
  var setOptions = {add: true, remove: true, merge: true};
  var addOptions = {add: true, remove: false};

  // Define the Collection's inheritable methods.
  _.extend(Collection.prototype, Events, {

    // The default model for a collection is just a **Backbone.Model**.
    // This should be overridden in most cases.
    model: Model,

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function(){},

    // The JSON representation of a Collection is an array of the
    // models' attributes.
    toJSON: function(options) {
      return this.map(function(model){ return model.toJSON(options); });
    },

    // Proxy `Backbone.sync` by default.
    sync: function() {
      return Backbone.sync.apply(this, arguments);
    },

    // Add a model, or list of models to the set.
    add: function(models, options) {
      return this.set(models, _.extend({merge: false}, options, addOptions));
    },

    // Remove a model, or a list of models from the set.
    remove: function(models, options) {
      var singular = !_.isArray(models);
      models = singular ? [models] : _.clone(models);
      options || (options = {});
      var i, l, index, model;
      for (i = 0, l = models.length; i < l; i++) {
        model = models[i] = this.get(models[i]);
        if (!model) continue;
        delete this._byId[model.id];
        delete this._byId[model.cid];
        index = this.indexOf(model);
        this.models.splice(index, 1);
        this.length--;
        if (!options.silent) {
          options.index = index;
          model.trigger('remove', model, this, options);
        }
        this._removeReference(model);
      }
      return singular ? models[0] : models;
    },

    // Update a collection by `set`-ing a new list of models, adding new ones,
    // removing models that are no longer present, and merging models that
    // already exist in the collection, as necessary. Similar to **Model#set**,
    // the core operation for updating the data contained by the collection.
    set: function(models, options) {
      options = _.defaults({}, options, setOptions);
      if (options.parse) models = this.parse(models, options);
      var singular = !_.isArray(models);
      models = singular ? (models ? [models] : []) : _.clone(models);
      var i, l, id, model, attrs, existing, sort;
      var at = options.at;
      var targetModel = this.model;
      var sortable = this.comparator && (at == null) && options.sort !== false;
      var sortAttr = _.isString(this.comparator) ? this.comparator : null;
      var toAdd = [], toRemove = [], modelMap = {};
      var add = options.add, merge = options.merge, remove = options.remove;
      var order = !sortable && add && remove ? [] : false;

      // Turn bare objects into model references, and prevent invalid models
      // from being added.
      for (i = 0, l = models.length; i < l; i++) {
        attrs = models[i];
        if (attrs instanceof Model) {
          id = model = attrs;
        } else {
          id = attrs[targetModel.prototype.idAttribute];
        }

        // If a duplicate is found, prevent it from being added and
        // optionally merge it into the existing model.
        if (existing = this.get(id)) {
          if (remove) modelMap[existing.cid] = true;
          if (merge) {
            attrs = attrs === model ? model.attributes : attrs;
            if (options.parse) attrs = existing.parse(attrs, options);
            existing.set(attrs, options);
            if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
          }
          models[i] = existing;

        // If this is a new, valid model, push it to the `toAdd` list.
        } else if (add) {
          model = models[i] = this._prepareModel(attrs, options);
          if (!model) continue;
          toAdd.push(model);

          // Listen to added models' events, and index models for lookup by
          // `id` and by `cid`.
          model.on('all', this._onModelEvent, this);
          this._byId[model.cid] = model;
          if (model.id != null) this._byId[model.id] = model;
        }
        if (order) order.push(existing || model);
      }

      // Remove nonexistent models if appropriate.
      if (remove) {
        for (i = 0, l = this.length; i < l; ++i) {
          if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
        }
        if (toRemove.length) this.remove(toRemove, options);
      }

      // See if sorting is needed, update `length` and splice in new models.
      if (toAdd.length || (order && order.length)) {
        if (sortable) sort = true;
        this.length += toAdd.length;
        if (at != null) {
          for (i = 0, l = toAdd.length; i < l; i++) {
            this.models.splice(at + i, 0, toAdd[i]);
          }
        } else {
          if (order) this.models.length = 0;
          var orderedModels = order || toAdd;
          for (i = 0, l = orderedModels.length; i < l; i++) {
            this.models.push(orderedModels[i]);
          }
        }
      }

      // Silently sort the collection if appropriate.
      if (sort) this.sort({silent: true});

      // Unless silenced, it's time to fire all appropriate add/sort events.
      if (!options.silent) {
        for (i = 0, l = toAdd.length; i < l; i++) {
          (model = toAdd[i]).trigger('add', model, this, options);
        }
        if (sort || (order && order.length)) this.trigger('sort', this, options);
      }
      
      // Return the added (or merged) model (or models).
      return singular ? models[0] : models;
    },

    // When you have more items than you want to add or remove individually,
    // you can reset the entire set with a new list of models, without firing
    // any granular `add` or `remove` events. Fires `reset` when finished.
    // Useful for bulk operations and optimizations.
    reset: function(models, options) {
      options || (options = {});
      for (var i = 0, l = this.models.length; i < l; i++) {
        this._removeReference(this.models[i]);
      }
      options.previousModels = this.models;
      this._reset();
      models = this.add(models, _.extend({silent: true}, options));
      if (!options.silent) this.trigger('reset', this, options);
      return models;
    },

    // Add a model to the end of the collection.
    push: function(model, options) {
      return this.add(model, _.extend({at: this.length}, options));
    },

    // Remove a model from the end of the collection.
    pop: function(options) {
      var model = this.at(this.length - 1);
      this.remove(model, options);
      return model;
    },

    // Add a model to the beginning of the collection.
    unshift: function(model, options) {
      return this.add(model, _.extend({at: 0}, options));
    },

    // Remove a model from the beginning of the collection.
    shift: function(options) {
      var model = this.at(0);
      this.remove(model, options);
      return model;
    },

    // Slice out a sub-array of models from the collection.
    slice: function() {
      return slice.apply(this.models, arguments);
    },

    // Get a model from the set by id.
    get: function(obj) {
      if (obj == null) return void 0;
      return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
    },

    // Get the model at the given index.
    at: function(index) {
      return this.models[index];
    },

    // Return models with matching attributes. Useful for simple cases of
    // `filter`.
    where: function(attrs, first) {
      if (_.isEmpty(attrs)) return first ? void 0 : [];
      return this[first ? 'find' : 'filter'](function(model) {
        for (var key in attrs) {
          if (attrs[key] !== model.get(key)) return false;
        }
        return true;
      });
    },

    // Return the first model with matching attributes. Useful for simple cases
    // of `find`.
    findWhere: function(attrs) {
      return this.where(attrs, true);
    },

    // Force the collection to re-sort itself. You don't need to call this under
    // normal circumstances, as the set will maintain sort order as each item
    // is added.
    sort: function(options) {
      if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
      options || (options = {});

      // Run sort based on type of `comparator`.
      if (_.isString(this.comparator) || this.comparator.length === 1) {
        this.models = this.sortBy(this.comparator, this);
      } else {
        this.models.sort(_.bind(this.comparator, this));
      }

      if (!options.silent) this.trigger('sort', this, options);
      return this;
    },

    // Pluck an attribute from each model in the collection.
    pluck: function(attr) {
      return _.invoke(this.models, 'get', attr);
    },

    // Fetch the default set of models for this collection, resetting the
    // collection when they arrive. If `reset: true` is passed, the response
    // data will be passed through the `reset` method instead of `set`.
    fetch: function(options) {
      options = options ? _.clone(options) : {};
      if (options.parse === void 0) options.parse = true;
      var success = options.success;
      var collection = this;
      options.success = function(resp) {
        var method = options.reset ? 'reset' : 'set';
        collection[method](resp, options);
        if (success) success(collection, resp, options);
        collection.trigger('sync', collection, resp, options);
      };
      wrapError(this, options);
      return this.sync('read', this, options);
    },

    // Create a new instance of a model in this collection. Add the model to the
    // collection immediately, unless `wait: true` is passed, in which case we
    // wait for the server to agree.
    create: function(model, options) {
      options = options ? _.clone(options) : {};
      if (!(model = this._prepareModel(model, options))) return false;
      if (!options.wait) this.add(model, options);
      var collection = this;
      var success = options.success;
      options.success = function(model, resp, options) {
        if (options.wait) collection.add(model, options);
        if (success) success(model, resp, options);
      };
      model.save(null, options);
      return model;
    },

    // **parse** converts a response into a list of models to be added to the
    // collection. The default implementation is just to pass it through.
    parse: function(resp, options) {
      return resp;
    },

    // Create a new collection with an identical list of models as this one.
    clone: function() {
      return new this.constructor(this.models);
    },

    // Private method to reset all internal state. Called when the collection
    // is first initialized or reset.
    _reset: function() {
      this.length = 0;
      this.models = [];
      this._byId  = {};
    },

    // Prepare a hash of attributes (or other model) to be added to this
    // collection.
    _prepareModel: function(attrs, options) {
      if (attrs instanceof Model) {
        if (!attrs.collection) attrs.collection = this;
        return attrs;
      }
      options = options ? _.clone(options) : {};
      options.collection = this;
      var model = new this.model(attrs, options);
      if (!model.validationError) return model;
      this.trigger('invalid', this, model.validationError, options);
      return false;
    },

    // Internal method to sever a model's ties to a collection.
    _removeReference: function(model) {
      if (this === model.collection) delete model.collection;
      model.off('all', this._onModelEvent, this);
    },

    // Internal method called every time a model in the set fires an event.
    // Sets need to update their indexes when models change ids. All other
    // events simply proxy through. "add" and "remove" events that originate
    // in other collections are ignored.
    _onModelEvent: function(event, model, collection, options) {
      if ((event === 'add' || event === 'remove') && collection !== this) return;
      if (event === 'destroy') this.remove(model, options);
      if (model && event === 'change:' + model.idAttribute) {
        delete this._byId[model.previous(model.idAttribute)];
        if (model.id != null) this._byId[model.id] = model;
      }
      this.trigger.apply(this, arguments);
    }

  });

  // Underscore methods that we want to implement on the Collection.
  // 90% of the core usefulness of Backbone Collections is actually implemented
  // right here:
  var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
    'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
    'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
    'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
    'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
    'lastIndexOf', 'isEmpty', 'chain'];

  // Mix in each Underscore method as a proxy to `Collection#models`.
  _.each(methods, function(method) {
    Collection.prototype[method] = function() {
      var args = slice.call(arguments);
      args.unshift(this.models);
      return _[method].apply(_, args);
    };
  });

  // Underscore methods that take a property name as an argument.
  var attributeMethods = ['groupBy', 'countBy', 'sortBy'];

  // Use attributes instead of properties.
  _.each(attributeMethods, function(method) {
    Collection.prototype[method] = function(value, context) {
      var iterator = _.isFunction(value) ? value : function(model) {
        return model.get(value);
      };
      return _[method](this.models, iterator, context);
    };
  });

  // Backbone.View
  // -------------

  // Backbone Views are almost more convention than they are actual code. A View
  // is simply a JavaScript object that represents a logical chunk of UI in the
  // DOM. This might be a single item, an entire list, a sidebar or panel, or
  // even the surrounding frame which wraps your whole app. Defining a chunk of
  // UI as a **View** allows you to define your DOM events declaratively, without
  // having to worry about render order ... and makes it easy for the view to
  // react to specific changes in the state of your models.

  // Creating a Backbone.View creates its initial element outside of the DOM,
  // if an existing element is not provided...
  var View = Backbone.View = function(options) {
    this.cid = _.uniqueId('view');
    options || (options = {});
    _.extend(this, _.pick(options, viewOptions));
    this._ensureElement();
    this.initialize.apply(this, arguments);
    this.delegateEvents();
  };

  // Cached regex to split keys for `delegate`.
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;

  // List of view options to be merged as properties.
  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

  // Set up all inheritable **Backbone.View** properties and methods.
  _.extend(View.prototype, Events, {

    // The default `tagName` of a View's element is `"div"`.
    tagName: 'div',

    // jQuery delegate for element lookup, scoped to DOM elements within the
    // current view. This should be preferred to global lookups where possible.
    $: function(selector) {
      return this.$el.find(selector);
    },

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function(){},

    // **render** is the core function that your view should override, in order
    // to populate its element (`this.el`), with the appropriate HTML. The
    // convention is for **render** to always return `this`.
    render: function() {
      return this;
    },

    // Remove this view by taking the element out of the DOM, and removing any
    // applicable Backbone.Events listeners.
    remove: function() {
      this.$el.remove();
      this.stopListening();
      return this;
    },

    // Change the view's element (`this.el` property), including event
    // re-delegation.
    setElement: function(element, delegate) {
      if (this.$el) this.undelegateEvents();
      this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
      this.el = this.$el[0];
      if (delegate !== false) this.delegateEvents();
      return this;
    },

    // Set callbacks, where `this.events` is a hash of
    //
    // *{"event selector": "callback"}*
    //
    //     {
    //       'mousedown .title':  'edit',
    //       'click .button':     'save',
    //       'click .open':       function(e) { ... }
    //     }
    //
    // pairs. Callbacks will be bound to the view, with `this` set properly.
    // Uses event delegation for efficiency.
    // Omitting the selector binds the event to `this.el`.
    // This only works for delegate-able events: not `focus`, `blur`, and
    // not `change`, `submit`, and `reset` in Internet Explorer.
    delegateEvents: function(events) {
      if (!(events || (events = _.result(this, 'events')))) return this;
      this.undelegateEvents();
      for (var key in events) {
        var method = events[key];
        if (!_.isFunction(method)) method = this[events[key]];
        if (!method) continue;

        var match = key.match(delegateEventSplitter);
        var eventName = match[1], selector = match[2];
        method = _.bind(method, this);
        eventName += '.delegateEvents' + this.cid;
        if (selector === '') {
          this.$el.on(eventName, method);
        } else {
          this.$el.on(eventName, selector, method);
        }
      }
      return this;
    },

    // Clears all callbacks previously bound to the view with `delegateEvents`.
    // You usually don't need to use this, but may wish to if you have multiple
    // Backbone views attached to the same DOM element.
    undelegateEvents: function() {
      this.$el.off('.delegateEvents' + this.cid);
      return this;
    },

    // Ensure that the View has a DOM element to render into.
    // If `this.el` is a string, pass it through `$()`, take the first
    // matching element, and re-assign it to `el`. Otherwise, create
    // an element from the `id`, `className` and `tagName` properties.
    _ensureElement: function() {
      if (!this.el) {
        var attrs = _.extend({}, _.result(this, 'attributes'));
        if (this.id) attrs.id = _.result(this, 'id');
        if (this.className) attrs['class'] = _.result(this, 'className');
        var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
        this.setElement($el, false);
      } else {
        this.setElement(_.result(this, 'el'), false);
      }
    }

  });

  // Backbone.sync
  // -------------

  // Override this function to change the manner in which Backbone persists
  // models to the server. You will be passed the type of request, and the
  // model in question. By default, makes a RESTful Ajax request
  // to the model's `url()`. Some possible customizations could be:
  //
  // * Use `setTimeout` to batch rapid-fire updates into a single request.
  // * Send up the models as XML instead of JSON.
  // * Persist models via WebSockets instead of Ajax.
  //
  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
  // as `POST`, with a `_method` parameter containing the true HTTP method,
  // as well as all requests with the body as `application/x-www-form-urlencoded`
  // instead of `application/json` with the model in a param named `model`.
  // Useful when interfacing with server-side languages like **PHP** that make
  // it difficult to read the body of `PUT` requests.
  Backbone.sync = function(method, model, options) {
    var type = methodMap[method];

    // Default options, unless specified.
    _.defaults(options || (options = {}), {
      emulateHTTP: Backbone.emulateHTTP,
      emulateJSON: Backbone.emulateJSON
    });

    // Default JSON-request options.
    var params = {type: type, dataType: 'json'};

    // Ensure that we have a URL.
    if (!options.url) {
      params.url = _.result(model, 'url') || urlError();
    }

    // Ensure that we have the appropriate request data.
    if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
      params.contentType = 'application/json';
      params.data = JSON.stringify(options.attrs || model.toJSON(options));
    }

    // For older servers, emulate JSON by encoding the request into an HTML-form.
    if (options.emulateJSON) {
      params.contentType = 'application/x-www-form-urlencoded';
      params.data = params.data ? {model: params.data} : {};
    }

    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
    // And an `X-HTTP-Method-Override` header.
    if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
      params.type = 'POST';
      if (options.emulateJSON) params.data._method = type;
      var beforeSend = options.beforeSend;
      options.beforeSend = function(xhr) {
        xhr.setRequestHeader('X-HTTP-Method-Override', type);
        if (beforeSend) return beforeSend.apply(this, arguments);
      };
    }

    // Don't process data on a non-GET request.
    if (params.type !== 'GET' && !options.emulateJSON) {
      params.processData = false;
    }

    // If we're sending a `PATCH` request, and we're in an old Internet Explorer
    // that still has ActiveX enabled by default, override jQuery to use that
    // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
    if (params.type === 'PATCH' && noXhrPatch) {
      params.xhr = function() {
        return new ActiveXObject("Microsoft.XMLHTTP");
      };
    }

    // Make the request, allowing the user to override any Ajax options.
    var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
    model.trigger('request', model, xhr, options);
    return xhr;
  };

  var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);

  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
  var methodMap = {
    'create': 'POST',
    'update': 'PUT',
    'patch':  'PATCH',
    'delete': 'DELETE',
    'read':   'GET'
  };

  // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
  // Override this if you'd like to use a different library.
  Backbone.ajax = function() {
    return Backbone.$.ajax.apply(Backbone.$, arguments);
  };

  // Backbone.Router
  // ---------------

  // Routers map faux-URLs to actions, and fire events when routes are
  // matched. Creating a new one sets its `routes` hash, if not set statically.
  var Router = Backbone.Router = function(options) {
    options || (options = {});
    if (options.routes) this.routes = options.routes;
    this._bindRoutes();
    this.initialize.apply(this, arguments);
  };

  // Cached regular expressions for matching named param parts and splatted
  // parts of route strings.
  var optionalParam = /\((.*?)\)/g;
  var namedParam    = /(\(\?)?:\w+/g;
  var splatParam    = /\*\w+/g;
  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;

  // Set up all inheritable **Backbone.Router** properties and methods.
  _.extend(Router.prototype, Events, {

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function(){},

    // Manually bind a single named route to a callback. For example:
    //
    //     this.route('search/:query/p:num', 'search', function(query, num) {
    //       ...
    //     });
    //
    route: function(route, name, callback) {
      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
      if (_.isFunction(name)) {
        callback = name;
        name = '';
      }
      if (!callback) callback = this[name];
      var router = this;
      Backbone.history.route(route, function(fragment) {
        var args = router._extractParameters(route, fragment);
        callback && callback.apply(router, args);
        router.trigger.apply(router, ['route:' + name].concat(args));
        router.trigger('route', name, args);
        Backbone.history.trigger('route', router, name, args);
      });
      return this;
    },

    // Simple proxy to `Backbone.history` to save a fragment into the history.
    navigate: function(fragment, options) {
      Backbone.history.navigate(fragment, options);
      return this;
    },

    // Bind all defined routes to `Backbone.history`. We have to reverse the
    // order of the routes here to support behavior where the most general
    // routes can be defined at the bottom of the route map.
    _bindRoutes: function() {
      if (!this.routes) return;
      this.routes = _.result(this, 'routes');
      var route, routes = _.keys(this.routes);
      while ((route = routes.pop()) != null) {
        this.route(route, this.routes[route]);
      }
    },

    // Convert a route string into a regular expression, suitable for matching
    // against the current location hash.
    _routeToRegExp: function(route) {
      route = route.replace(escapeRegExp, '\\$&')
                   .replace(optionalParam, '(?:$1)?')
                   .replace(namedParam, function(match, optional) {
                     return optional ? match : '([^\/]+)';
                   })
                   .replace(splatParam, '(.*?)');
      return new RegExp('^' + route + '$');
    },

    // Given a route, and a URL fragment that it matches, return the array of
    // extracted decoded parameters. Empty or unmatched parameters will be
    // treated as `null` to normalize cross-browser behavior.
    _extractParameters: function(route, fragment) {
      var params = route.exec(fragment).slice(1);
      return _.map(params, function(param) {
        return param ? decodeURIComponent(param) : null;
      });
    }

  });

  // Backbone.History
  // ----------------

  // Handles cross-browser history management, based on either
  // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
  // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
  // and URL fragments. If the browser supports neither (old IE, natch),
  // falls back to polling.
  var History = Backbone.History = function() {
    this.handlers = [];
    _.bindAll(this, 'checkUrl');

    // Ensure that `History` can be used outside of the browser.
    if (typeof window !== 'undefined') {
      this.location = window.location;
      this.history = window.history;
    }
  };

  // Cached regex for stripping a leading hash/slash and trailing space.
  var routeStripper = /^[#\/]|\s+$/g;

  // Cached regex for stripping leading and trailing slashes.
  var rootStripper = /^\/+|\/+$/g;

  // Cached regex for detecting MSIE.
  var isExplorer = /msie [\w.]+/;

  // Cached regex for removing a trailing slash.
  var trailingSlash = /\/$/;

  // Cached regex for stripping urls of hash and query.
  var pathStripper = /[?#].*$/;

  // Has the history handling already been started?
  History.started = false;

  // Set up all inheritable **Backbone.History** properties and methods.
  _.extend(History.prototype, Events, {

    // The default interval to poll for hash changes, if necessary, is
    // twenty times a second.
    interval: 50,

    // Gets the true hash value. Cannot use location.hash directly due to bug
    // in Firefox where location.hash will always be decoded.
    getHash: function(window) {
      var match = (window || this).location.href.match(/#(.*)$/);
      return match ? match[1] : '';
    },

    // Get the cross-browser normalized URL fragment, either from the URL,
    // the hash, or the override.
    getFragment: function(fragment, forcePushState) {
      if (fragment == null) {
        if (this._hasPushState || !this._wantsHashChange || forcePushState) {
          fragment = this.location.pathname;
          var root = this.root.replace(trailingSlash, '');
          if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
        } else {
          fragment = this.getHash();
        }
      }
      return fragment.replace(routeStripper, '');
    },

    // Start the hash change handling, returning `true` if the current URL matches
    // an existing route, and `false` otherwise.
    start: function(options) {
      if (History.started) throw new Error("Backbone.history has already been started");
      History.started = true;

      // Figure out the initial configuration. Do we need an iframe?
      // Is pushState desired ... is it available?
      this.options          = _.extend({root: '/'}, this.options, options);
      this.root             = this.options.root;
      this._wantsHashChange = this.options.hashChange !== false;
      this._wantsPushState  = !!this.options.pushState;
      this._hasPushState    = !!(this.options.pushState && this.history && this.history.pushState);
      var fragment          = this.getFragment();
      var docMode           = document.documentMode;
      var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));

      // Normalize root to always include a leading and trailing slash.
      this.root = ('/' + this.root + '/').replace(rootStripper, '/');

      if (oldIE && this._wantsHashChange) {
        this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
        this.navigate(fragment);
      }

      // Depending on whether we're using pushState or hashes, and whether
      // 'onhashchange' is supported, determine how we check the URL state.
      if (this._hasPushState) {
        Backbone.$(window).on('popstate', this.checkUrl);
      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
        Backbone.$(window).on('hashchange', this.checkUrl);
      } else if (this._wantsHashChange) {
        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
      }

      // Determine if we need to change the base url, for a pushState link
      // opened by a non-pushState browser.
      this.fragment = fragment;
      var loc = this.location;
      var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;

      // Transition from hashChange to pushState or vice versa if both are
      // requested.
      if (this._wantsHashChange && this._wantsPushState) {

        // If we've started off with a route from a `pushState`-enabled
        // browser, but we're currently in a browser that doesn't support it...
        if (!this._hasPushState && !atRoot) {
          this.fragment = this.getFragment(null, true);
          this.location.replace(this.root + this.location.search + '#' + this.fragment);
          // Return immediately as browser will do redirect to new url
          return true;

        // Or if we've started out with a hash-based route, but we're currently
        // in a browser where it could be `pushState`-based instead...
        } else if (this._hasPushState && atRoot && loc.hash) {
          this.fragment = this.getHash().replace(routeStripper, '');
          this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
        }

      }

      if (!this.options.silent) return this.loadUrl();
    },

    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
    // but possibly useful for unit testing Routers.
    stop: function() {
      Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
      clearInterval(this._checkUrlInterval);
      History.started = false;
    },

    // Add a route to be tested when the fragment changes. Routes added later
    // may override previous routes.
    route: function(route, callback) {
      this.handlers.unshift({route: route, callback: callback});
    },

    // Checks the current URL to see if it has changed, and if it has,
    // calls `loadUrl`, normalizing across the hidden iframe.
    checkUrl: function(e) {
      var current = this.getFragment();
      if (current === this.fragment && this.iframe) {
        current = this.getFragment(this.getHash(this.iframe));
      }
      if (current === this.fragment) return false;
      if (this.iframe) this.navigate(current);
      this.loadUrl();
    },

    // Attempt to load the current URL fragment. If a route succeeds with a
    // match, returns `true`. If no defined routes matches the fragment,
    // returns `false`.
    loadUrl: function(fragment) {
      fragment = this.fragment = this.getFragment(fragment);
      return _.any(this.handlers, function(handler) {
        if (handler.route.test(fragment)) {
          handler.callback(fragment);
          return true;
        }
      });
    },

    // Save a fragment into the hash history, or replace the URL state if the
    // 'replace' option is passed. You are responsible for properly URL-encoding
    // the fragment in advance.
    //
    // The options object can contain `trigger: true` if you wish to have the
    // route callback be fired (not usually desirable), or `replace: true`, if
    // you wish to modify the current URL without adding an entry to the history.
    navigate: function(fragment, options) {
      if (!History.started) return false;
      if (!options || options === true) options = {trigger: !!options};

      var url = this.root + (fragment = this.getFragment(fragment || ''));

      // Strip the fragment of the query and hash for matching.
      fragment = fragment.replace(pathStripper, '');

      if (this.fragment === fragment) return;
      this.fragment = fragment;

      // Don't include a trailing slash on the root.
      if (fragment === '' && url !== '/') url = url.slice(0, -1);

      // If pushState is available, we use it to set the fragment as a real URL.
      if (this._hasPushState) {
        this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);

      // If hash changes haven't been explicitly disabled, update the hash
      // fragment to store history.
      } else if (this._wantsHashChange) {
        this._updateHash(this.location, fragment, options.replace);
        if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
          // Opening and closing the iframe tricks IE7 and earlier to push a
          // history entry on hash-tag change.  When replace is true, we don't
          // want this.
          if(!options.replace) this.iframe.document.open().close();
          this._updateHash(this.iframe.location, fragment, options.replace);
        }

      // If you've told us that you explicitly don't want fallback hashchange-
      // based history, then `navigate` becomes a page refresh.
      } else {
        return this.location.assign(url);
      }
      if (options.trigger) return this.loadUrl(fragment);
    },

    // Update the hash location, either replacing the current entry, or adding
    // a new one to the browser history.
    _updateHash: function(location, fragment, replace) {
      if (replace) {
        var href = location.href.replace(/(javascript:|#).*$/, '');
        location.replace(href + '#' + fragment);
      } else {
        // Some browsers require that `hash` contains a leading #.
        location.hash = '#' + fragment;
      }
    }

  });

  // Create the default Backbone.history.
  Backbone.history = new History;

  // Helpers
  // -------

  // Helper function to correctly set up the prototype chain, for subclasses.
  // Similar to `goog.inherits`, but uses a hash of prototype properties and
  // class properties to be extended.
  var extend = function(protoProps, staticProps) {
    var parent = this;
    var child;

    // The constructor function for the new subclass is either defined by you
    // (the "constructor" property in your `extend` definition), or defaulted
    // by us to simply call the parent's constructor.
    if (protoProps && _.has(protoProps, 'constructor')) {
      child = protoProps.constructor;
    } else {
      child = function(){ return parent.apply(this, arguments); };
    }

    // Add static properties to the constructor function, if supplied.
    _.extend(child, parent, staticProps);

    // Set the prototype chain to inherit from `parent`, without calling
    // `parent`'s constructor function.
    var Surrogate = function(){ this.constructor = child; };
    Surrogate.prototype = parent.prototype;
    child.prototype = new Surrogate;

    // Add prototype properties (instance properties) to the subclass,
    // if supplied.
    if (protoProps) _.extend(child.prototype, protoProps);

    // Set a convenience property in case the parent's prototype is needed
    // later.
    child.__super__ = parent.prototype;

    return child;
  };

  // Set up inheritance for the model, collection, router, view and history.
  Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;

  // Throw an error when a URL is needed, and none is supplied.
  var urlError = function() {
    throw new Error('A "url" property or function must be specified');
  };

  // Wrap an optional error callback with a fallback error event.
  var wrapError = function(model, options) {
    var error = options.error;
    options.error = function(resp) {
      if (error) error(model, resp, options);
      model.trigger('error', model, resp, options);
    };
  };

}).call(this);

define("backbone", ["underscore"], (function (global) {
    return function () {
        var ret, fn;
        return ret || global.Backbone;
    };
}(this)));

/*global define */

define('grapher/bar-graph/bar-graph-model',['require','backbone'],function (require) {
  // Dependencies.
  var Backbone = require('backbone'),

      BarGraphModel = Backbone.Model.extend({
        defaults: {
          // Current value displayed by bar graph.
          value:     0,
          // Second value displayed by bar graph (using small triangle).
          // It can be used to show averaged or previous value.
          // null means that it shouldn't be displayed at all.
          secondValue: null,
          // Min value displayed.
          min: 0,
          // Max value displayed.
          max: 10,

          // Width of the bar graph (bar itself, labels, titles etc. are
          // NOT included).
          barWidth: "2em",

          // Height of the bar graph container (bar itself + small padding).
          height: "20em",

          // Graph title. You can also specify multiline title using array
          // of strings, e.g.:
          // ["Title", "Subtitle"]
          title: "",
          // Accepted values are "right", "top" and "bottom".
          titleOn: "right",
          // Color of the main bar.
          barColor:  "#e23c34",
          // Color of the area behind the bar.
          fillColor: "white",
          // Number of labels displayed on the left side of the graph.
          // This value is *only* a suggestion. The most clean
          // and human-readable values are used.
          // You can also specify value-label pairs, e.g.:
          // [
          //   {
          //     "value": 0,
          //     "label": "low"
          //   },
          //   {
          //     "value": 10,
          //     "label": "high"
          //   }
          // ]
          // Use 0 or null to disable labels completely.
          labels:          10,
          // Units symbol displayed next to labels.
          units: "",
          // Number of grid lines displayed on the bar.
          gridLines:      10,
          // Format of labels.
          // See the specification of this format:
          // https://github.com/mbostock/d3/wiki/Formatting#wiki-d3_format
          // or:
          // http://docs.python.org/release/3.1.3/library/string.html#formatspec
          labelFormat: "0.1f"
        }
      });

  return BarGraphModel;
});

/*global define, $ */

/**
 * Require this module to initialize Lab jQuery plugins.
 */
define('common/jquery-plugins',[],function () {
  /**
   * Allows to measure element when it isn't already added to the page.
   * @param  {Function} fn       Function which will be executed.
   * @param  {string}   selector jQuery selector.
   * @param  {Object}   parent   Element which will be used as a temporal container.
   * @return {*}                 Result of 'fn' execution.
   */
  $.fn.measure = function(fn, selector, parent) {
    var el, selection, result;
    el = $(this).clone(false);
    el.css({
      visibility: 'hidden',
      position: 'absolute'
    });
    el.appendTo(parent);
    if (selector) {
      selection = el.find(selector);
    } else {
      selection = el;
    }
    result = fn.apply(selection);
    el.remove();
    return result;
  };

  /**
   * Truncates text inside given element, so its width doesn't exceed specified
   * value (in pixels). Note that you *can* use this function even on elements
   * like <p> or <h1>, which quite often have width of its parent (not width of
   * their text). This function will create a new <span> element with the same
   * style as original text and use it to measure real width of the text.
   *
   * @param  {number} maxWidth Maximum allowed width of text.
   */
  $.fn.truncate = function (maxWidth) {
    var $el = $(this),
        $span = $('<span>'),
        width,
        newText;

    $span.text($el.text());
    $span.css({
      'font-size': $el.css('font-size'),
      'font-weight': $el.css('font-weight'),
      'white-space': 'nowrap',
      'visibility': 'hidden'
    });
    $span.appendTo($el.parent());

    width = $span.width();

    if (width > maxWidth) {
      newText = $span.text() + "...";
      $span.text(newText);
      while (width > maxWidth && newText.length > 3) {
        newText = $span.text().slice(0, -4) + "...";
        $span.text(newText);
        width = $span.width();
      }

      // Save original text content in title attribute,
      // so tooltip can be displayed.
      $el.attr("title", $el.text());
      // Update original element.
      $el.text(newText);
    }
    // Cleanup!
    $span.remove();
  };

 /**
  * jQuery alterClass plugin
  *
  * Remove element classes with wildcard matching. Optionally add classes:
  *   $( '#foo' ).alterClass( 'foo-* bar-*', 'foobar' )
  *
  * Copyright (c) 2011 Pete Boere (the-echoplex.net)
  * Free under terms of the MIT license: http://www.opensource.org/licenses/mit-license.php
  *
  * source: https://gist.github.com/peteboere/1517285
  */
  $.fn.alterClass = function (removals, additions) {
    var self = this;

    if ( removals.indexOf( '*' ) === -1 ) {
      // Use native jQuery methods if there is no wildcard matching
      self.removeClass( removals );
      return !additions ? self : self.addClass( additions );
    }

    var patt = new RegExp( '\\s' +
        removals.
          replace( /\*/g, '[A-Za-z0-9-_]+' ).
          split( ' ' ).
          join( '\\s|\\s' ) +
        '\\s', 'g' );

    self.each( function ( i, it ) {
      var cn = ' ' + it.className + ' ';
      while ( patt.test( cn ) ) {
        cn = cn.replace( patt, ' ' );
      }
      it.className = $.trim( cn );
    });

    return !additions ? self : self.addClass( additions );
  };

  /**
    * jQuery special event triggered when element is removed from DOM.
    * e.g. $('#element-id').on('destroyed', function () { console.log('destroyed!'); })
    */
  $.event.special.destroyed = {
    remove: function(o) {
      if (o.handler) {
        o.handler()
      }
    }
  };
});

/*global define, d3, $ */

define('grapher/bar-graph/bar-graph-view',['require','common/jquery-plugins','backbone'],function (require) {
  //  Dependencies.
      require('common/jquery-plugins');
  var Backbone  = require('backbone'),

      uid = 0,
      // Returns unique ID used by the bar graph view.
      getUID = function () {
        return uid++;
      },

      // Get real width SVG of element using bounding box.
      getRealWidth = function (d3selection) {
        return d3selection.node().getBBox().width;
      },

      // Bar graph scales itself according to the font size.
      // We assume some CANONICAL_FONT_SIZE. All values which should
      // be scaled, should use returned function.
      CANONICAL_FONT_SIZE = 16,
      getScaleFunc = function (fontSize) {
        var factor = fontSize / CANONICAL_FONT_SIZE;

        return function (val) {
          return val * factor;
        };
      },

      setupValueLabelPairs = function (yAxis, ticks) {
        var values = [],
            labels = {},
            i, len;

        for (i = 0, len = ticks.length; i < len; i++) {
          values[i] = ticks[i].value;
          labels[values[i]] = ticks[i].label;
        }

        yAxis
          .tickValues(values)
          .tickFormat(function (value) {
            return labels[value];
          });
      },

      getFormatFunc = function (formatString, unitsString) {
        var format = d3.format(formatString);
        return function (value) {
          return format(value) + " " + unitsString;
        };
      },

      BarGraphView = Backbone.View.extend({
        // Container is a DIV.
        tagName: "div",

        className: "bar-graph",

        initialize: function () {
          // Unique ID. Required to generate unique
          // gradient names.
          this.uid = getUID();

          this.$topArea = $('<div class="top-area">').appendTo(this.$el);

          // Create some SVG elements, which are constant and doesn't need to
          // be recreated each time during rendering.
          this.vis = d3.select(this.el).append("svg");
          this.defs = this.vis.append("defs");
          this.axisContainer = this.vis.append("g");
          this.fill = this.vis.append("rect");
          this.bar = this.vis.append("rect");
          this.gridContainer = this.vis.append("g");
          this.trianglePos = this.vis.append("g");
          this.traingle = this.trianglePos.append("polygon");
          this.titleContainer = this.vis.append("g");

          this.yScale = d3.scale.linear();
          this.heightScale = d3.scale.linear();
          this.yAxis = d3.svg.axis();

          this.scale = null;
          this.barWidth = null;

          this.$bottomArea = $('<div class="bottom-area">').appendTo(this.$el);

          // Register callbacks!
          this.model.on("change", this.modelChanged, this);
        },

        // Render whole bar graph.
        render: function () {
              // toJSON() returns all attributes of the model.
              // This is equivalent to many calls like:
              // property1 = model.get("property1");
              // property2 = model.get("property2");
              // etc.
          var options            = this.model.toJSON(),
              fontSize           = parseFloat(this.$el.css("font-size")),
              // Scale function.
              scale = this.scale = getScaleFunc(fontSize),
              renderLabels       = options.labels > 0 || options.labels.length > 0,
              // Basic padding (scaled).
              paddingTop         = renderLabels ? scale(8) : scale(3),
              paddingBottom      = renderLabels ? scale(8) : scale(3),

              offset = 0;

          // Set height of the most outer container.
          this.$el.outerHeight(options.height);

          this._setupHorizontalTitle();

          this.svgHeight = this.$el.height() - this.$topArea.height() - this.$bottomArea.height();

          // Setup SVG element.
          this.vis
            .attr({
              // Use some random width. At the end of rendering, it will be
              // updated to a valid value in ems (based on the graph content).
              "width":  600,
              "height": this.svgHeight
            });

          // Setup Y scale.
          this.yScale
            .domain([options.min, options.max])
            .range([this.svgHeight - paddingTop, paddingBottom])
            .clamp(true);

          // Setup scale used to translation of the bar height.
          this.heightScale
            .domain([options.min, options.max])
            .range([0, this.svgHeight - paddingTop - paddingBottom])
            .clamp(true);

          // Render elements from left to right.

          this.axisContainer.selectAll("*").remove();
          if (renderLabels) {
            // Setup Y axis.
            this.yAxis
              .scale(this.yScale)
              .tickValues(null)
              .tickPadding(0)
              .tickSize(0, 0, 0)
              .orient("left");

            if (typeof options.labels === "number") {
              // Just normal tics.
              this.yAxis
                .ticks(options.labels)
                .tickFormat(getFormatFunc(options.labelFormat, options.units));
            } else {
              // Array with value - label pairs.
              setupValueLabelPairs(this.yAxis, options.labels);
            }

            // Create and append Y axis.
            this.axisContainer.call(this.yAxis);

            offset += getRealWidth(this.axisContainer);

            this.axisContainer.attr("transform", "translate(" + offset + ")");

            offset += scale(5);
          }

          // Setup background of the bar.
          this.fill
            .attr({
              "width": options.barWidth,
              "height": this.heightScale(options.max),
              "x": offset,
              "y": this.yScale(options.max),
              "rx": "0.5em",
              "ry": "0.5em",
              "fill": this._getFillGradient(options.fillColor)
            });

          // Setup the main bar.
          this.bar
            .attr({
              "width": options.barWidth,
              "x": offset,
              "rx": "0.5em",
              "ry": "0.5em",
              "fill": this._getBarGradient(options.barColor)
            });

          this.barWidth = getRealWidth(this.fill);

          this.traingle
            .classed("triangle", true)
            .attr({
              "points": "15,-7 15,7 1,0",
              "transform": "translate(" + offset + ") scale(" + scale(1) + ")"
            });

          this._setupGrid(offset);

          offset += this.barWidth;

          offset = this._setupTitle(offset);

          // Convert final width in px into value in ems.
          // That ensures that the SVG will work well with semantic layout.
          this.vis.attr("width", (offset / fontSize) + "em");
          this.$el.css("min-width", (offset / fontSize) + "em");

          // work-around bug on iPad2 where container is not expanding in width
          // when SVG element rendered inside it
          // see: Bar graph rendering issues on iPad
          // https://www.pivotaltracker.com/story/show/47854951
          // This means while we are duplicating the current padding styles set
          // in _grapher.sass changes in desired style must be duplicated here.
          this.$el.css("min-width", (offset / fontSize + 0.8) + "em");

          // Finally, update displayed values.
          this.update();
        },

        // Updates only bar height.
        update: function () {
          var value       = this.model.get("value"),
              secondValue = this.model.get("secondValue");

          this.bar
            .attr("height", this.heightScale(value))
            .attr("y", this.yScale(value));

          if (typeof secondValue !== 'undefined' && secondValue !== null) {
            this.traingle.classed("hidden", false);
            this.trianglePos.attr("transform", "translate(0," + this.yScale(secondValue) + ")");
          } else {
            this.traingle.classed("hidden", true);
          }
        },

        // This function should be called whenever model attribute is changed.
        modelChanged: function () {
          var changedAttributes = this.model.changedAttributes(),
              count = 0,
              valChanged, secValChanged, name;

          // There are two possible cases:
          // - Only "value" or "secondValue" have changed, so update only values
          //   displays.
          // - Other attributes have changed, so redraw whole bar graph.

          // Case 1. Check how many attributes have been changed.
          for (name in changedAttributes) {
            if (changedAttributes.hasOwnProperty(name)) {
              count++;
              if (count > 2) {
                // If 3 or more, redraw whole bar graph.
                this.render();
                return;
              }
            }
          }

          valChanged = typeof changedAttributes.value !== 'undefined';
          secValChanged = typeof changedAttributes.secondValue !== 'undefined';
          // Case 2. 1 or 2 attributes have changed, check if they are "value" and "secondValue".
          if ((count === 1 && (valChanged || secValChanged)) ||
              (count === 2 && (valChanged && secValChanged))) {
            this.update();
          } else {
            this.render();
          }
        },

        _getBarGradient: function (color) {
          var id = "bar-gradient-" + this.uid,
              gradient = this.defs.select("#" + id);

          color = d3.rgb(color);

          if (gradient.empty()) {
            // Create a new gradient.
            gradient = this.defs.append("linearGradient")
              .attr("id", id)
              .attr("x1", "0%")
              .attr("y1", "0%")
              .attr("x2", "0%")
              .attr("y2", "100%");
          } else {
            gradient.selectAll("stop").remove();
          }

          gradient.append("stop")
            .attr("stop-color", color.brighter(2).toString())
            .attr("offset", "0%");
          gradient.append("stop")
            .attr("stop-color", color.toString())
            .attr("offset", "100%");

          return "url(#" + id + ")";
        },

        _getFillGradient: function (color) {
          var id = "fill-gradient-" + this.uid,
              gradient = this.defs.select("#" + id);

          if (gradient.empty()) {
            // Create a new gradient.
            gradient = this.defs.append("linearGradient")
              .attr("id", id)
              .attr("x1", "0%")
              .attr("y1", "0%")
              .attr("x2", "0%")
              .attr("y2", "100%");
          } else {
            gradient.selectAll("stop").remove();
          }

          gradient.append("stop")
            .attr("stop-color", color)
            .attr("offset", "0%");
          gradient.append("stop")
            .attr("stop-color", color)
            .attr("stop-opacity", 0.5)
            .attr("offset", "15%");
          gradient.append("stop")
            .attr("stop-color", color)
            .attr("stop-opacity", 0.4)
            .attr("offset", "100%");

          return "url(#" + id + ")";
        },

        _setupGrid: function (offset) {
          var gridLines = this.yScale.ticks(this.model.get("gridLines")),
              yScale = this.yScale,
              width = this.barWidth;

          // Remove first and last tick, as we don't want to draw it as grid line.
          gridLines.pop(); gridLines.shift();
          this.grid = this.gridContainer.selectAll(".grid-line").data(gridLines, String),

          this.grid.enter().append("path").attr("class", "grid-line");
          this.grid.exit().remove();
          this.grid.attr("d", function (d) {
            return "M " + offset + " " + Math.round(yScale(d)) + " h " + width;
          });

          return offset;
        },

        // Setup vertical title.
        _setupTitle: function (offset) {
              // "title" option is expected to be string
              // or array of strings.
          var title = this.model.get("title"),
              self  = this,
              isArray, lines,
              titleG, gEnter;

          if (title && this.model.get("titleOn") === "right") {
            offset += this.scale(10);

            isArray = $.isArray(title);
            lines = isArray ? title.length : 1;

            titleG = this.titleContainer.selectAll(".title").data(isArray ? title : [title]);

            titleG.exit().remove();

            gEnter = titleG.enter().append("g").attr("class", "title");
            gEnter.append("title");
            gEnter.append("text");

            titleG.each(function (d, i) {
              var g = d3.select(this);
              g.select("title").text(d);
              g.select("text")
                .text(self._processTitle(d))
                .attr("dy", -(lines - i -1) + "em");
            });

            // Transform whole container.
            this.titleContainer.attr("transform",
              "translate(" + offset + ", " + this.svgHeight / 2 + ") rotate(90)");

            // Update offset.
            offset += parseFloat($(titleG.node()).css("font-size")) * lines;
          }

          return offset;
        },

        // Setup horizontal title.
        _setupHorizontalTitle: function () {
              // "title" option is expected to be string
              // or array of strings.
          var title = this.model.get("title"),
              pos = this.model.get("titleOn"),
              $container;

          this.$topArea.empty();
          this.$bottomArea.empty();

          if (!title || title.length === 0 || pos === "right") {
            return;
          }

          title = $.isArray(title) ? title : [title];

          if (pos === "top") {
            $container = this.$topArea;
          } else if (pos === "bottom") {
            $container = this.$bottomArea;
          }

          title.forEach(function (t) {
            $container.append('<p class="title">' + t + '</p>');
          });
        },

        _processTitle: function (title) {
          var $title = $('<span class="title">' + title + '</span>').appendTo(this.$el),
              truncatedText;

          $title.truncate(this.svgHeight);
          truncatedText = $title.text();
          $title.remove();
          return truncatedText;
        }
      });

  return BarGraphView;
});

/*global define */

/**
 * Tiny "mixin" that can be used by an interactive component.
 */
define('common/controllers/help-icon-support',[],function () {

  return function helpIconSupport(component, componentDef, helpSystem) {
    if (componentDef.helpIcon) {
      var $helpIcon = $('<i class="icon-question-sign lab-help-icon lab-component-help-icon"></i>');
      $helpIcon.on('click', function () {
        if (!helpSystem.isActive()) {
          helpSystem.showSingle(componentDef.id);
        }
      });
      $helpIcon.appendTo(component.getViewContainer());
    }
  };
});

/*global $: false, define: false */

// Bar graph controller.
// It provides specific interface used in MD2D environment
// (by interactives-controller and layout module).
define('common/controllers/bar-graph-controller',['require','grapher/bar-graph/bar-graph-model','grapher/bar-graph/bar-graph-view','common/controllers/interactive-metadata','common/controllers/help-icon-support','common/validator'],function (require) {
  var BarGraphModel   = require('grapher/bar-graph/bar-graph-model'),
      BarGraphView    = require('grapher/bar-graph/bar-graph-view'),
      metadata        = require('common/controllers/interactive-metadata'),
      helpIconSupport = require('common/controllers/help-icon-support'),
      validator       = require('common/validator'),

      // Note: We always explicitly copy properties from component spec to bar graph options hash,
      // in order to avoid tighly coupling an externally-exposed API (the component spec) to an
      // internal implementation detail (the bar graph options format).
      barGraphOptionForComponentSpecProperty = {
        // Min value displayed.
        min: 'min',
        // Max value displayed.
        max: 'max',
        // Graph title.
        title: 'title',
        // Title position.
        titleOn: 'titleOn',
        // Color of the main bar.
        barColor:  'barColor',
        // Color of the area behind the bar.
        fillColor: 'fillColor',
        // Number of labels displayed on the left side of the graph.
        // This value is *only* a suggestion. The most clean
        // and human-readable values are used.
        // You can also specify value-label pairs, e.g.:
        // [
        //   {
        //     "value": 0,
        //     "label": "low"
        //   },
        //   {
        //     "value": 10,
        //     "label": "high"
        //   }
        // ]
        // Use 0 or null to disable labels completely.
        labels:      'labels',
        // Number of grid lines displayed on the bar.
        // This value is *only* a suggestion, it's similar to 'ticks'.
        gridLines:  'gridLines',
        // Format of labels.
        // See the specification of this format:
        // https://github.com/mbostock/d3/wiki/Formatting#wiki-d3_format
        // or:
        // http://docs.python.org/release/3.1.3/library/string.html#formatspec
        labelFormat: 'labelFormat',
        // Units displayed next to labels. Set it to 'true' to use units
        // automatically retrieved from property description. Set it to any
        // string to use custom unit symbol.
        units: 'units'
      },

      // Limit options only to these supported.
      filterOptions = function(inputHash) {
        var options = {},
            cName, gName;

        for (cName in barGraphOptionForComponentSpecProperty) {
          if (barGraphOptionForComponentSpecProperty.hasOwnProperty(cName)) {
            gName = barGraphOptionForComponentSpecProperty[cName];
            if (inputHash[cName] !== undefined) {
              options[gName] = inputHash[cName];
            }
          }
        }
        return options;
      };

  return function BarGraphController(component, interactivesController) {
    var // Object with Public API.
        controller,
        model,
        // Model with options and current value.
        barGraphModel,
        // Main view.
        barGraphView,
        // First data channel.
        property,
        // Second data channel.
        secondProperty,

        update = function () {
          barGraphModel.set({value: model.get(property)});
        },

        updateSecondProperty = function () {
          barGraphModel.set({secondValue: model.get(secondProperty)});
        };

    function initialize() {
      model = interactivesController.getModel();

      // Validate component definition, use validated copy of the properties.
      component = validator.validateCompleteness(metadata.barGraph, component);
      barGraphModel = new BarGraphModel(filterOptions(component));
      barGraphView  = new BarGraphView({model: barGraphModel, id: component.id});
      // Each interactive component has to have class "component".
      barGraphView.$el.addClass("component");
      property = component.property;
      secondProperty = component.secondProperty;

      if (component.tooltip) {
        barGraphView.$el.attr("title", component.tooltip);
      }

      helpIconSupport(controller, component, interactivesController.helpSystem);
    }

    controller = {
      // This callback should be triggered when model is loaded.
      modelLoadedCallback: function () {
        var units = "";
        if (model) {
          model.removeObserver(property, update);
          if (secondProperty) {
            model.removeObserver(secondProperty, updateSecondProperty);
          }
        }
        model = interactivesController.getModel();
        // Register properties listeners.
        model.addPropertiesListener([property], update);
        if (typeof component.averagePeriod !== 'undefined' && component.averagePeriod !== null) {
          // This option is for authors convenience. It causes that filtered
          // output is automatically defined (it uses basic property as an
          // input). Author doesn't have to define it manually.
          secondProperty = property + "-bargraph-" + component.id + "-average";
          model.defineFilteredOutput(secondProperty, {}, property, "RunningAverage", component.averagePeriod);
        }
        if (secondProperty) {
          model.addPropertiesListener([secondProperty], updateSecondProperty);
        }
        // Retrieve and set units if they are enabled.
        if (component.units === true) {
          // Units automatically retrieved from property description.
          units = model.getPropertyDescription(property).getUnitAbbreviation();
        } else if (component.units) {
          // Units defined in JSON definition explicitly.
          units = component.units;
        }
        // Apply custom width and height settings.
        // Do it in modelLoadedCallback, as during its execution,
        // the view container is already added to the document and
        // calculations of the size work correctly.
        // Also, pass calculated unit type.
        barGraphModel.set({
          barWidth: component.barWidth,
          height: component.height,
          units: units
        });
        // Initial render...
        barGraphView.render();
        // and update.
        update();
      },

      // Returns view container (div).
      getViewContainer: function () {
        return barGraphView.$el;
      },

      // Method required by layout module.
      resize: function () {
        // Just render bar graph again.
        barGraphView.render();
      },

      // Returns serialized component definition.
      serialize: function () {
        var result = $.extend(true, {}, component);
        // Return updated definition.
        return result;
      }
    };

    initialize();

    // Return Public API object.
    return controller;
  };
});

/*global define, $*/

define('common/controllers/graph-controller',['require','lab-grapher','common/controllers/interactive-metadata','common/validator','common/listening-pool','common/controllers/data-set','common/controllers/help-icon-support'],function (require) {
  var Graph           = require('lab-grapher'),
      metadata        = require('common/controllers/interactive-metadata'),
      validator       = require('common/validator'),
      ListeningPool   = require('common/listening-pool'),
      DataSet         = require('common/controllers/data-set'),
      helpIconSupport = require('common/controllers/help-icon-support'),

      // Note: We always explicitly copy properties from component spec to grapher options hash,
      // in order to avoid tighly coupling an externally-exposed API (the component spec) to an
      // internal implementation detail (the grapher options format).
      grapherOptionForComponentSpecProperty = {
        title: 'title',
        titlePosition: 'titlePosition',
        buttonsStyle: 'buttonsStyle',
        buttonsLayout: 'buttonsLayout',
        enableAutoScaleButton: 'enableAutoScaleButton',
        enableAxisScaling: 'enableAxisScaling',
        enableZooming: 'enableZooming',
        autoScaleX: 'autoScaleX',
        autoScaleY: 'autoScaleY',
        enableSelectionButton: 'enableSelectionButton',
        clearSelectionOnLeavingSelectMode: 'clearSelectionOnLeavingSelectMode',
        enableDrawButton: 'enableDrawButton',
        drawIndex: 'drawIndex',
        dataPoints: 'dataPoints',
        markAllDataPoints: 'markAllDataPoints',
        showRulersOnSelection: 'showRulersOnSelection',
        fontScaleRelativeToParent: 'fontScaleRelativeToParent',
        hideAxisValues: 'hideAxisValues',
        xlabel: 'xlabel',
        xmin: 'xmin',
        xmax: 'xmax',
        ylabel: 'ylabel',
        ymin: 'ymin',
        ymax: 'ymax',
        lineWidth: 'lineWidth',
        xTickCount: 'xTickCount',
        yTickCount: 'yTickCount',
        xscaleExponent: 'xscaleExponent',
        yscaleExponent: 'yscaleExponent',
        xFormatter: 'xFormatter',
        yFormatter: 'yFormatter',
        lines: 'lines',
        bars: 'bars',
        dataColors: 'dataColors',
        legendLabels: 'legendLabels',
        legendVisible: 'legendVisible'
      },

      graphControllerCount = 0,

      // Index of the model property whose description sets the current X/Y label
      // (when labels aren't provided explicitly in graph component description).
      X_LABEL_PROP_IDX = 0,
      Y_LABEL_PROP_IDX = 0;

  return function graphController(component, interactivesController) {
    var // HTML element containing view
        $container,
        grapher,
        controller,
        dataSet,
        scriptingAPI,
        xProperties,
        properties,
        dataPointsArrays = [],
        staticSeries,
        listeningPool,
        ignoreDataSetEvents = false,
        suppressDomainSync = false,
        namespace = "graphController" + (++graphControllerCount);

    function getModel () {
      return interactivesController.getModel();
    }

    // Returns true if label is defined explicitly (it's defined and different from "auto").
    function isLabelExplicit(label) {
      return label != null && label !== "auto";
    }

    function loadDataSet () {
      // Get public data set (if its name is provided) or create own, private data set that will
      // be used only by this graph.
      if (component.dataSet) {
        dataSet = interactivesController.getDataSet(component.dataSet);
      } else {
        // Make sure that properties passed to data set include xProperty!
        var dataSetProperties = component.properties.slice();
        xProperties.forEach(function (xProp) {
          if (dataSetProperties.indexOf(xProp) === -1) {
            dataSetProperties.push(xProp);
          }
        });
        dataSet = new DataSet({
                                properties:          dataSetProperties,
                                name:                component.id + "-autoDataSet",
                                streamDataFromModel: component.streamDataFromModel,
                                clearOnModelReset:   component.clearOnModelReset
                              }, interactivesController, true);
      }
      listeningPool.listen(dataSet, DataSet.Events.DATA_RESET,        _dataResetHandler);
      listeningPool.listen(dataSet, DataSet.Events.SAMPLE_ADDED,      _sampleAddedHandler);
      listeningPool.listen(dataSet, DataSet.Events.SAMPLE_CHANGED,    _sampleChangedHandler);
      listeningPool.listen(dataSet, DataSet.Events.SAMPLE_REMOVED,    _sampleRemovedHandler);
      listeningPool.listen(dataSet, DataSet.Events.SELECTION_CHANGED, _selectionChangeHandler);
      listeningPool.listen(dataSet, DataSet.Events.DATA_TRUNCATED,    _invalidationHandler);
      listeningPool.listen(dataSet, DataSet.Events.LABELS_CHANGED,    _labelsChangedHandler);
    }

    function initialize() {
      scriptingAPI = interactivesController.getScriptingAPI();
      listeningPool = new ListeningPool(namespace);
      // Validate component definition, use validated copy of the properties.
      component = validator.validateCompleteness(metadata.graph, component);
      // The list of properties we are being asked to graph.
      properties = component.properties.slice();
      xProperties = component.xProperty;
      if (!$.isArray(xProperties)) {
        xProperties = [xProperties];
      }
      loadDataSet();
      $container = $('<div>').attr('id', component.id).addClass('graph');
      // Each interactive component has to have class "component".
      $container.addClass("component");
      // Apply custom width and height settings.
      $container.css({
        width: component.width,
        height: component.height
      });
      if (component.tooltip) {
        $container.attr("title", component.tooltip);
      }
      // Support optional add help icon.
      helpIconSupport(controller, component, interactivesController.helpSystem);

      staticSeries = [];

      if (component.drawProperty) {
        component.drawIndex = properties.indexOf(component.drawProperty);
        if (component.drawIndex == -1) {
          component.drawProperty = properties[0];
          component.drawIndex = 0;
        }
      }

      // Initial setup of the data.
      dataSet.resetData();
    }

    /**
      Return an options hash for use by the grapher.
    */
    function getOptions() {
      var options = {},
          cProp,
          gOption;

      // update grapher options from component spec & defaults
      for (cProp in grapherOptionForComponentSpecProperty) {
        if (grapherOptionForComponentSpecProperty.hasOwnProperty(cProp)) {
          gOption = grapherOptionForComponentSpecProperty[cProp];
          options[gOption] = component[cProp];
        }
      }

      // These options are specific for Lab and require some more work that just copying
      // values.
      if (component.syncXAxis) {
        setupAxisSync('x', component.syncXAxis, options);
      }
      if (component.syncYAxis) {
        setupAxisSync('y', component.syncYAxis, options);
      }

      return options;
    }

    function setupAxisSync(axis, syncedGraphs, options) {
      var callbackName = 'on'  + axis.toUpperCase() + 'DomainChange'; // e.g. onXDomainChange
      var setterName   = 'set' + axis.toUpperCase() + 'Domain';       // e.g. setXDomain
      if (typeof(syncedGraphs) === 'string') {
        syncedGraphs = [syncedGraphs];
      }
      options[callbackName] = function(min, max) {
        if (suppressDomainSync) return;
        syncedGraphs.forEach(function(syncedGraphID) {
          var syncedGraph = interactivesController.getComponent(syncedGraphID);
          // Third argument (true) ensures that synchronization will be suppressed in target graph.
          // It prevents us from creating infinite loop when we have two-way synchronization.
          syncedGraph[setterName](min, max, true);
        });
      }
    }

    /**
      Causes the graph to move the "current" pointer to the current model step. This desaturates
      the graph region corresponding to times after the current point.
    */
    function redrawCurrentStepPointer(step) {
      grapher.updateOrRescale(step);
    }
    function _selectionChangeHandler(evt) {
      if (ignoreDataSetEvents) return;
      redrawCurrentStepPointer(evt.data);
    }

    function resetGraph() {
      if (grapher) {
        if (component.resetAxesOnReset) {
          resetGrapher();
        }
      } else {
        initGrapher();
      }
      clearGrapher(dataSet.getData());
      updateLabels();
    }
    function _modelResetHandler() {
      resetGraph();
    }

    /**
      Reset all the datapoints in the graph.
      dataSeriesArry will contain an empty data set, or invitial values
      for all model params.
    */
    function clearGrapher(data) {
      if (!grapher) return;
      // Convert data received from data set to data format expected by grapher (nested arrays).
      var gData = [];
      properties.forEach(function (prop, idx) {
        var gSeries = [];
        var xArr = data[xProp(idx)];
        var propArr = data[prop];
        for (var i = 0, len = Math.min(xArr.length, propArr.length); i < len; i++) {
          gSeries.push([xArr[i], propArr[i]]);
        }
        gData.push(gSeries);
      });

      // Append static data series!
      gData = gData.concat(staticSeries);

      grapher.resetPoints(gData);
      grapher.repaint();
    }

    function _dataResetHandler(extra) {
      if (ignoreDataSetEvents) return;
      clearGrapher(extra.data);
    }
    function _invalidationHandler(extra) {
      if (ignoreDataSetEvents) return;
      clearGrapher(extra.data);
    }
    function _labelsChangedHandler(labels) {
      if (ignoreDataSetEvents) return;
      var labelWasChanged = false;
      if (!isLabelExplicit(component.ylabel)) {
        // Set label provided by dataset only if graph component description doesn't specify ylabel.
        var yProp = properties[Y_LABEL_PROP_IDX];

        // If the change is triggered via listener, the values will be wrapped in the 'data' property...
        var yLabel = labels[yProp] || labels.data[yProp];
        grapher.yLabel(yLabel);
        labelWasChanged = true;
      }
      if (!isLabelExplicit(component.xlabel)) {
        var xProp = xProperties[X_LABEL_PROP_IDX];
        var xLabel = labels[xProp] || labels.data[xProp];
        grapher.xLabel(xLabel);
        labelWasChanged = true;
      }
      if (labelWasChanged) {
        controller.syncAxisRangesToPropertyRanges();
      }
    }

    /**
      Ask the grapher to reset itself, without adding new data.
    */
    function resetGrapher() {
      grapher.reset('#' + component.id, getOptions());
    }

    function isPointValid(point) {
      var x = point[0];
      var y = point[1];
      return x != null && x !== "" && !isNaN(Number(x)) &&
             y != null && y !== "" && !isNaN(Number(y));
    }

    function xProp(idx) {
      return xProperties[idx] || xProperties[0];
    }

    function _sampleAddedHandler(evt) {
      if (!grapher) return;
      if (ignoreDataSetEvents) return;
      // Convert data received from data set to data expected by grapher (nested arrays).
      var valid = true;
      var dataPoint = evt.data;
      var gPoints = [];
      var point;
      properties.forEach(function (prop, idx) {
        point = [dataPoint[xProp(idx)], dataPoint[prop]];
        // Pass only valid points, null will be ignored by grapher.
        gPoints.push(isPointValid(point) ? point : null);
      });
      if (valid) grapher.addPoints(gPoints);
    }

    function _sampleChangedHandler(evt) {
      if (!grapher) return;
      if (ignoreDataSetEvents) return;
      var dataPoint = evt.data.dataPoint;
      var index = evt.data.index;
      var gPoints = [];
      var point;
      properties.forEach(function (prop, idx) {
        point = [dataPoint[xProp(idx)], dataPoint[prop]];
        // Pass only valid points, null will be ignored by grapher.
        gPoints.push(isPointValid(point) ? point : null);
      });
      grapher.replacePoints(gPoints, index);
    }

    function _sampleRemovedHandler(evt) {
      if (!grapher) return;
      if (ignoreDataSetEvents) return;
      var index = evt.data.index,
          props = evt.data.props;
      properties.forEach(function (prop, propIdx) {
        if (props.indexOf(prop) !== -1) {
          grapher.deletePoint(index, propIdx);
        }
      });
    }

    function registerModelListeners() {
      var model = getModel();
      // We reset the graph view after model reset.
      model.on('reset', _modelResetHandler);
    }

    function updateLabels() {
      _labelsChangedHandler(dataSet.getLabels());
    }

    function graphChangedDataPoint(evt) {
      ignoreDataSetEvents = true;
      var yPro = component.drawProperty || properties[0],
          xPro = xProp(properties.indexOf(yPro)),
          data = {};
      data[xPro] = evt.point[0];
      data[yPro] = evt.point[1];
      if (evt.action === "added") {
        dataSet.appendDataPoint([xPro, yPro], data);
      } else if (evt.action === "removed") {
        // Make sure that data has both X and Y values, so the point can be clearly identified.
        // X values don't have to be unique - grapher sometimes adds the same point twice...
        var idx = dataSet.dataPointIndex(data);
        // Remove only Y property value, X property can be shared.
        dataSet.removeDataPoint([yPro], idx);
      }
      ignoreDataSetEvents = false;
    }

    function initGrapher() {
      grapher = new Graph($container[0], getOptions(), undefined, interactivesController.getNextTabIndex());
      grapher.addPointListener(graphChangedDataPoint);
    }

    controller = {
      type: "graph",

      /**
        Called by the interactives controller when the model finishes loading.
      */
      modelLoadedCallback: function() {
        registerModelListeners();
        scriptingAPI = interactivesController.getScriptingAPI();
        resetGraph();
        grapher.repaint();
      },

      getDataSet: function() {
        return dataSet;
      },

      /**
        Used when manually adding points to the graph.
      */
      appendDataPropertiesToComponent: function() {
        dataSet.appendDataPoint();
      },


      /**
        Add non-realtime series to the dataSet.
      */
      addDataSet: function (series) {
        staticSeries.push(series);
      },

      /**
        Remove all non-realtime data series from the dataSets
      */
      clearDataSets: function () {
        staticSeries = [];
      },

      /**
        Modifies the current list of graph options with new values and resets the graph.the
        Note: does not support changes to the 'properties' list.
      */
      setAttributes: function(opts) {
        if (grapher) {
          $.extend(component, opts);
          dataSet.resetData();
          if (opts.dataPoints) {
            dataPointsArrays = opts.dataPoints;
          }
          resetGrapher();
          // We may have set or unset the explicit 'ylabel' / 'xlabel' options; update the graph's
          // labels as appropriate.
          updateLabels();
        }
      },

      /**
        Sets X domain of the graph without clearing the data.
      */
      setXDomain: function(min, max, suppressSync) {
        if (grapher) {
          suppressDomainSync = suppressSync;
          grapher.xDomain([min, max]);
          suppressDomainSync = false;
        }
      },

      /**
        Sets Y domain of the graph without clearing the data.
      */
      setYDomain: function(min, max, suppressSync) {
        if (grapher) {
          suppressDomainSync = suppressSync;
          grapher.yDomain([min, max]);
          suppressDomainSync = false;
        }
      },

      /**
        Adjusts axis ranges to match those of the properties the graph is reading from, without
        clearing data.

        Does nothing to the x-axis if the description of the xProperty has no min or max property.
        For the y-axis properties, finds the (min, max) pair that contains all property ranges,
        ignoring missing values for min or max, as long as at least one property has a min and one
        property has a max.
      */
      syncAxisRangesToPropertyRanges: function() {
        var model = getModel();
        var xDescription = model.getPropertyDescription(xProperties[X_LABEL_PROP_IDX]);
        var yDescriptions = properties.map(function(property) {
          return model.getPropertyDescription(property);
        });
        var ymin;
        var ymax;

        if (xDescription && xDescription.getMin() != null && xDescription.getMax() != null) {
          grapher.xDomain([xDescription.getMin(), xDescription.getMax()]);
        }

        ymin = Infinity;
        ymax = -Infinity;
        yDescriptions.forEach(function(description) {
          if (description) {
            if (description.getMin() < ymin) ymin = description.getMin();
            if (description.getMax() > ymax) ymax = description.getMax();
          }
        });

        if (isFinite(ymin) && isFinite(ymax)) {
          grapher.yDomain([ymin, ymax]);
        }
      },

      /**
        If the x=0 is not visible in the current x axis range, move the x-axis so that x=0 is
        present at the left of the graph, while keeping the current x axis scale and the y axis
        range.
      */
      scrollXAxisToZero: function() {
        var xmin = grapher.xmin();
        var xmax = grapher.xmax();

        if (xmin !== 0) {
          grapher.xDomain([0, xmax - xmin]);
        }
      },

      /**
        Returns the grapher object itself.
      */
      getView: function() {
        return grapher;
      },

      /**
        Returns a jQuery selection containing the div which contains the graph.
      */
      getViewContainer: function() {
        return $container;
      },

      resize: function () {
        // For now only "fit to parent" behavior is supported.
        if (grapher) {
          grapher.resize();
        }
      },

      reset: function () {
        if (grapher) {
          resetGrapher();
        }
      },

      update: function () {
        if (grapher) {
          grapher.update();
        }
      },

      selectionDomain: function() {
        if (grapher) {
          return grapher.selectionDomain.apply(grapher, arguments);
        }
        return null;
      },

      selectionEnabled: function() {
        if (grapher) {
          return grapher.selectionEnabled.apply(grapher, arguments);
        }
        return null;
      },

      addAnnotation: function(props) {
        if (grapher) {
          grapher.addAnnotation(props);
        }
      },

      resetAnnotations: function(){
        if (grapher) {
          grapher.resetAnnotations();
        }
      },

      /**
        Returns serialized component definition.
      */
      serialize: function () {
        // The only thing which needs to be updated is scaling of axes.
        // Note however that the serialized definition should always have
        // 'xmin' set to initial value, as after deserialization we assume
        // that there is no previous data and simulations should start from the beginning.
        var result = $.extend(true, {}, component),
            // Get current domains settings, e.g. after dragging performed by the user.
            // TODO: this should be reflected somehow in the grapher model,
            // not grabbed directly from the view as now. Waiting for refactoring.
            xDomain = grapher.xDomain(),
            yDomain = grapher.yDomain(),
            startX  = component.xmin;

        result.ymin = yDomain[0];
        result.ymax = yDomain[1];
        // Shift graph back to the original origin, but keep scale of the X axis.
        // This is not very clear, but follows the rule of least surprise for the user.
        result.xmin = startX;
        result.xmax = startX + xDomain[1] - xDomain[0];

        return result;
      },

      enableLogging: function (logFunc) {
        // If logging gets more complicated, I think we should move the logic to the grapher itself
        // and here just call something like: grapher.enableLogging(logFunc);
        $container.off('.logging');
        $container.on('click.logging', '.graph-button.legend', function () {
          logFunc('GraphKeyBtnClicked');
        });
        $container.on('click.logging', '.graph-button.autoscale', function () {
          logFunc('GraphZoomBtnClicked');
        });
      }
    };

    initialize();
    return controller;

  };
});

!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define('iframe-phone',e):"undefined"!=typeof window?window.iframePhone=e():"undefined"!=typeof global?global.iframePhone=e():"undefined"!=typeof self&&(self.iframePhone=e())}(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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.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(require,module,exports){
var structuredClone = require('./structured-clone');
var HELLO_INTERVAL_LENGTH = 200;
var HELLO_TIMEOUT_LENGTH = 60000;

function IFrameEndpoint() {
  var listeners = {};
  var isInitialized = false;
  var connected = false;
  var postMessageQueue = [];
  var helloInterval;

  function postToParent(message) {
    // See http://dev.opera.com/articles/view/window-postmessage-messagechannel/#crossdoc
    //     https://github.com/Modernizr/Modernizr/issues/388
    //     http://jsfiddle.net/ryanseddon/uZTgD/2/
    if (structuredClone.supported()) {
      window.parent.postMessage(message, '*');
    } else {
      window.parent.postMessage(JSON.stringify(message), '*');
    }
  }

  function post(type, content) {
    var message;
    // Message object can be constructed from 'type' and 'content' arguments or it can be passed
    // as the first argument.
    if (arguments.length === 1 && typeof type === 'object' && typeof type.type === 'string') {
      message = type;
    } else {
      message = {
        type: type,
        content: content
      };
    }
    if (connected) {
      postToParent(message);
    } else {
      postMessageQueue.push(message);
    }
  }

  function postHello() {
    postToParent({
      type: 'hello'
    });
  }

  function addListener(type, fn) {
    listeners[type] = fn;
  }

  function removeAllListeners() {
    listeners = {};
  }

  function getListenerNames() {
    return Object.keys(listeners);
  }

  function messageListener(message) {
    // Anyone can send us a message. Only pay attention to messages from parent.
    if (message.source !== window.parent) return;
    var messageData = message.data;
    if (typeof messageData === 'string') messageData = JSON.parse(messageData);

    if (!connected && messageData.type === 'hello') {
      connected = true;
      stopPostingHello();
      while (postMessageQueue.length > 0) {
        post(postMessageQueue.shift());
      }
    }

    if (connected && listeners[messageData.type]) {
      listeners[messageData.type](messageData.content);
    }
  }

  function disconnect() {
    connected = false;
    stopPostingHello();
    window.removeEventListener('message', messsageListener);
  }

  /**
    Initialize communication with the parent frame. This should not be called until the app's custom
    listeners are registered (via our 'addListener' public method) because, once we open the
    communication, the parent window may send any messages it may have queued. Messages for which
    we don't have handlers will be silently ignored.
  */
  function initialize() {
    if (isInitialized) {
      return;
    }
    isInitialized = true;
    if (window.parent === window) return;

    // We kick off communication with the parent window by sending a "hello" message. Then we wait
    // for a handshake (another "hello" message) from the parent window.
    startPostingHello();
    window.addEventListener('message', messageListener, false);
  }

  function startPostingHello() {
    if (helloInterval) {
      stopPostingHello();
    }
    helloInterval = window.setInterval(postHello, HELLO_INTERVAL_LENGTH);
    window.setTimeout(stopPostingHello, HELLO_TIMEOUT_LENGTH);
    // Post the first msg immediately.
    postHello();
  }

  function stopPostingHello() {
    window.clearInterval(helloInterval);
    helloInterval = null;
  }

  // Public API.
  return {
    initialize: initialize,
    getListenerNames: getListenerNames,
    addListener: addListener,
    removeAllListeners: removeAllListeners,
    disconnect: disconnect,
    post: post
  };
}

var instance = null;

// IFrameEndpoint is a singleton, as iframe can't have multiple parents anyway.
module.exports = function getIFrameEndpoint() {
  if (!instance) {
    instance = new IFrameEndpoint();
  }
  return instance;
};

},{"./structured-clone":4}],2:[function(require,module,exports){
var ParentEndpoint = require('./parent-endpoint');
var getIFrameEndpoint = require('./iframe-endpoint');

// Not a real UUID as there's an RFC for that (needed for proper distributed computing).
// But in this fairly parochial situation, we just need to be fairly sure to avoid repeats.
function getPseudoUUID() {
  var chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
  var len = chars.length;
  var ret = [];

  for (var i = 0; i < 10; i++) {
    ret.push(chars[Math.floor(Math.random() * len)]);
  }
  return ret.join('');
}

module.exports = function IframePhoneRpcEndpoint(handler, namespace, targetWindow, targetOrigin, phone) {
  var pendingCallbacks = Object.create({});

  // if it's a non-null object, rather than a function, 'handler' is really an options object
  if (handler && typeof handler === 'object') {
    namespace = handler.namespace;
    targetWindow = handler.targetWindow;
    targetOrigin = handler.targetOrigin;
    phone = handler.phone;
    handler = handler.handler;
  }

  if (!phone) {
    if (targetWindow === window.parent) {
      phone = getIFrameEndpoint();
      phone.initialize();
    } else {
      phone = new ParentEndpoint(targetWindow, targetOrigin);
    }
  }

  phone.addListener(namespace, function (message) {
    var callbackObj;

    if (message.messageType === 'call' && typeof this.handler === 'function') {
      this.handler.call(undefined, message.value, function (returnValue) {
        phone.post(namespace, {
          messageType: 'returnValue',
          uuid: message.uuid,
          value: returnValue
        });
      });
    } else if (message.messageType === 'returnValue') {
      callbackObj = pendingCallbacks[message.uuid];

      if (callbackObj) {
        window.clearTimeout(callbackObj.timeout);
        if (callbackObj.callback) {
          callbackObj.callback.call(undefined, message.value);
        }
        pendingCallbacks[message.uuid] = null;
      }
    }
  }.bind(this));

  function call(message, callback) {
    var uuid = getPseudoUUID();

    pendingCallbacks[uuid] = {
      callback: callback,
      timeout: window.setTimeout(function () {
        if (callback) {
          callback(undefined, new Error("IframePhone timed out waiting for reply"));
        }
      }, 2000)
    };

    phone.post(namespace, {
      messageType: 'call',
      uuid: uuid,
      value: message
    });
  }

  function disconnect() {
    phone.disconnect();
  }

  this.handler = handler;
  this.call = call.bind(this);
  this.disconnect = disconnect.bind(this);
};

},{"./iframe-endpoint":1,"./parent-endpoint":3}],3:[function(require,module,exports){
var structuredClone = require('./structured-clone');

/**
  Call as:
    new ParentEndpoint(targetWindow, targetOrigin, afterConnectedCallback)
      targetWindow is a WindowProxy object. (Messages will be sent to it)

      targetOrigin is the origin of the targetWindow. (Messages will be restricted to this origin)

      afterConnectedCallback is an optional callback function to be called when the connection is
        established.

  OR (less secure):
    new ParentEndpoint(targetIframe, afterConnectedCallback)

      targetIframe is a DOM object (HTMLIframeElement); messages will be sent to its contentWindow.

      afterConnectedCallback is an optional callback function

    In this latter case, targetOrigin will be inferred from the value of the src attribute of the
    provided DOM object at the time of the constructor invocation. This is less secure because the
    iframe might have been navigated to an unexpected domain before constructor invocation.

  Note that it is important to specify the expected origin of the iframe's content to safeguard
  against sending messages to an unexpected domain. This might happen if our iframe is navigated to
  a third-party URL unexpectedly. Furthermore, having a reference to Window object (as in the first
  form of the constructor) does not protect against sending a message to the wrong domain. The
  window object is actualy a WindowProxy which transparently proxies the Window object of the
  underlying iframe, so that when the iframe is navigated, the "same" WindowProxy now references a
  completely differeent Window object, possibly controlled by a hostile domain.

  See http://www.esdiscuss.org/topic/a-dom-use-case-that-can-t-be-emulated-with-direct-proxies for
  more about this weird behavior of WindowProxies (the type returned by <iframe>.contentWindow).
*/

module.exports = function ParentEndpoint(targetWindowOrIframeEl, targetOrigin, afterConnectedCallback) {
  var postMessageQueue = [];
  var connected = false;
  var handlers = {};
  var targetWindowIsIframeElement;

  function getIframeOrigin(iframe) {
    return iframe.src.match(/(.*?\/\/.*?)\//)[1];
  }

  function post(type, content) {
    var message;
    // Message object can be constructed from 'type' and 'content' arguments or it can be passed
    // as the first argument.
    if (arguments.length === 1 && typeof type === 'object' && typeof type.type === 'string') {
      message = type;
    } else {
      message = {
        type: type,
        content: content
      };
    }
    if (connected) {
      var tWindow = getTargetWindow();
      // if we are laready connected ... send the message
      // See http://dev.opera.com/articles/view/window-postmessage-messagechannel/#crossdoc
      //     https://github.com/Modernizr/Modernizr/issues/388
      //     http://jsfiddle.net/ryanseddon/uZTgD/2/
      if (structuredClone.supported()) {
        tWindow.postMessage(message, targetOrigin);
      } else {
        tWindow.postMessage(JSON.stringify(message), targetOrigin);
      }
    } else {
      // else queue up the messages to send after connection complete.
      postMessageQueue.push(message);
    }
  }

  function addListener(messageName, func) {
    handlers[messageName] = func;
  }

  function removeListener(messageName) {
    handlers[messageName] = null;
  }

  // Note that this function can't be used when IFrame element hasn't been added to DOM yet
  // (.contentWindow would be null). At the moment risk is purely theoretical, as the parent endpoint
  // only listens for an incoming 'hello' message and the first time we call this function
  // is in #receiveMessage handler (so iframe had to be initialized before, as it could send 'hello').
  // It would become important when we decide to refactor the way how communication is initialized.
  function getTargetWindow() {
    if (targetWindowIsIframeElement) {
      var tWindow = targetWindowOrIframeEl.contentWindow;
      if (!tWindow) {
        throw "IFrame element needs to be added to DOM before communication " +
              "can be started (.contentWindow is not available)";
      }
      return tWindow;
    }
    return targetWindowOrIframeEl;
  }

  function receiveMessage(message) {
    var messageData;
    if (message.source === getTargetWindow() && (targetOrigin === '*' || message.origin === targetOrigin)) {
      messageData = message.data;
      if (typeof messageData === 'string') {
        messageData = JSON.parse(messageData);
      }
      if (handlers[messageData.type]) {
        handlers[messageData.type](messageData.content);
      } else {
        console.log("cant handle type: " + messageData.type);
      }
    }
  }

  function disconnect() {
    connected = false;
    window.removeEventListener('message', receiveMessage);
  }

  // handle the case that targetWindowOrIframeEl is actually an <iframe> rather than a Window(Proxy) object
  // Note that if it *is* a WindowProxy, this probe will throw a SecurityException, but in that case
  // we also don't need to do anything
  try {
    targetWindowIsIframeElement = targetWindowOrIframeEl.constructor === HTMLIFrameElement;
  } catch (e) {
    targetWindowIsIframeElement = false;
  }

  if (targetWindowIsIframeElement) {
    // Infer the origin ONLY if the user did not supply an explicit origin, i.e., if the second
    // argument is empty or is actually a callback (meaning it is supposed to be the
    // afterConnectionCallback)
    if (!targetOrigin || targetOrigin.constructor === Function) {
      afterConnectedCallback = targetOrigin;
      targetOrigin = getIframeOrigin(targetWindowOrIframeEl);
    }
  }

  // Handle pages served through file:// protocol. Behaviour varies in different browsers. Safari sets origin
  // to 'file://' and everything works fine, but Chrome and Safari set message.origin to null.
  // Also, https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage says:
  //  > Lastly, posting a message to a page at a file: URL currently requires that the targetOrigin argument be "*".
  //  > file:// cannot be used as a security restriction; this restriction may be modified in the future.
  // So, using '*' seems like the only possible solution.
  if (targetOrigin === 'file://') {
    targetOrigin = '*';
  }

  // when we receive 'hello':
  addListener('hello', function () {
    connected = true;

    // send hello response
    post({
      type: 'hello',
      // `origin` property isn't used by IframeEndpoint anymore (>= 1.2.0), but it's being sent to be
      // backward compatible with old IframeEndpoint versions (< v1.2.0).
      origin: window.location.href.match(/(.*?\/\/.*?)\//)[1]
    });

    // give the user a chance to do things now that we are connected
    // note that is will happen before any queued messages
    if (afterConnectedCallback && typeof afterConnectedCallback === "function") {
      afterConnectedCallback();
    }

    // Now send any messages that have been queued up ...
    while (postMessageQueue.length > 0) {
      post(postMessageQueue.shift());
    }
  });

  window.addEventListener('message', receiveMessage, false);

  // Public API.
  return {
    post: post,
    addListener: addListener,
    removeListener: removeListener,
    disconnect: disconnect,
    getTargetWindow: getTargetWindow,
    targetOrigin: targetOrigin
  };
};

},{"./structured-clone":4}],4:[function(require,module,exports){
var featureSupported = false;

(function () {
  var result = 0;

  if (!!window.postMessage) {
    try {
      // Safari 5.1 will sometimes throw an exception and sometimes won't, lolwut?
      // When it doesn't we capture the message event and check the
      // internal [[Class]] property of the message being passed through.
      // Safari will pass through DOM nodes as Null iOS safari on the other hand
      // passes it through as DOMWindow, gotcha.
      window.onmessage = function (e) {
        var type = Object.prototype.toString.call(e.data);
        result = (type.indexOf("Null") != -1 || type.indexOf("DOMWindow") != -1) ? 1 : 0;
        featureSupported = {
          'structuredClones': result
        };
      };
      // Spec states you can't transmit DOM nodes and it will throw an error
      // postMessage implimentations that support cloned data will throw.
      window.postMessage(document.createElement("a"), "*");
    } catch (e) {
      // BBOS6 throws but doesn't pass through the correct exception
      // so check error message
      result = (e.DATA_CLONE_ERR || e.message == "Cannot post cyclic structures.") ? 1 : 0;
      featureSupported = {
        'structuredClones': result
      };
    }
  }
}());

exports.supported = function supported() {
  return featureSupported && featureSupported.structuredClones > 0;
};

},{}],5:[function(require,module,exports){
module.exports = {
  /**
   * Allows to communicate with an iframe.
   */
  ParentEndpoint:  require('./lib/parent-endpoint'),
  /**
   * Allows to communicate with a parent page.
   * IFrameEndpoint is a singleton, as iframe can't have multiple parents anyway.
   */
  getIFrameEndpoint: require('./lib/iframe-endpoint'),
  structuredClone: require('./lib/structured-clone'),

  // TODO: May be misnamed
  IframePhoneRpcEndpoint: require('./lib/iframe-phone-rpc-endpoint')

};

},{"./lib/iframe-endpoint":1,"./lib/iframe-phone-rpc-endpoint":2,"./lib/parent-endpoint":3,"./lib/structured-clone":4}]},{},[5])
(5)
});
;
/*jshint eqnull: true */
/*global define */

define('import-export/dg-exporter',['require','lab.config','iframe-phone'],function(require) {

  var config  = require('lab.config');
  var iframePhone = require('iframe-phone');

  /*
    Private method. Listener for messages sent from CODAP via the iframePhone RPC endpoint.

    Currently, the only message from CODAP that we listen for is the 'codap-present' message
    indicating that we are embedded in an iframePhone-capable CODAP instance. When this message is
    received, `this.codapDidConnect` (a method to be added by client code) is invoked, if present.

    message:   message sent by iframePhone
    callback:  callback passed by iframePhone; must be called to acknowledge receipt of message
  */
  function codapCallbackHandler(message, callback) {
    var wasConnected;
    if (message && message.message === 'codap-present') {
      wasConnected = this.isCodapPresent;

      this.isCodapPresent = true;

      // Some simple (but very limited) zero-configuration event listening:
      if ( ! wasConnected  && typeof this.codapDidConnect === 'function' ) {
        this.codapDidConnect();
      }
    }
    callback();
  }

  var dgExporter = {

    gameName: 'Next Gen MW',

    parentTableLabels: {
      singleCase: "run",
      pluralCase: "runs",
      singleCaseWithArticle: "a run",
      setOfCases: "set",
      setOfCasesWithArticle: "a set"
    },

    childTableLabels: {
      singleCase: "measurement",
      pluralCase: "measurements",
      singleCaseWithArticle: "a measurement",
      setOfCases: "time series",
      setOfCasesWithArticle: "a time series"
    },

    singleTableLabels: {
      singleCase: "measurement",
      pluralCase: "measurements",
      singleCaseWithArticle: "a measurement",
      setOfCases: "set",
      setOfCasesWithArticle: "a set"
    },

    perRunColumnLabelCount: 0,
    perRunColumnLabelPositions: {},

    isCodapPresent: false,

    init: function() {
      if (this.codapPhone) return; // nothing to initialize
      this.codapPhone = new iframePhone.IframePhoneRpcEndpoint(
        codapCallbackHandler.bind(this),
        "codap-game",
        window.parent
      );
    },

    canCallDGDirect: function() {
      if (config.codap || config.dataGamesProxyPrefix) {
        try {
          if (window.parent.DG.doCommand) {
            return true;
          }
        } catch (e) {
          // could be a security exception if window.parent is not same-origin, or a ReferenceError
          // if the game controller isn't defined; in either case, fall through.
        }
      }
      return false;
    },

    isEmbeddedInCODAP: function() {
      return this.isCodapPresent || this.canCallDGDirect();
    },

    canExportData: function() {
      return this.isEmbeddedInCODAP();
    },

    doCommand: function(name, args, callback) {
      var cmd = {
        action: name,
        args: args
      };

      // Ensure the "direct" path follows an async execution pattern, because the iframePhone path
      // is unavoidably async. APIs that call back synchronously sometimes, async other times
      // release Zalgo: http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony

      if (this.canCallDGDirect()) {
        setTimeout(function() {
          var result = window.parent.DG.doCommand(cmd);
          if (callback) {
            setTimeout(function() {
                callback(result);
            }, 1);
          }
        }, 1);
      } else if (this.isCodapPresent) {
        this.codapPhone.call(cmd, callback);
      }
    },

    /**
      Exports the summary data about a run as 1 CODAP table and exports timeseries data, if any, as
      a second, linked table.

      perRunLabels: list of column labels for the "left" table which contains a summary of the run
        (this can contain parameters that define the run, as well as )

      perRunData: list containing 1 row of data to be added to the left table

      timeSeriesLabels (optional): List of column labels for the "right" table which contains a
        set of time points that will be linked to the single row which is added to the "left", run-
        summary table

        If no timeSeriesLabels are provided, the linked "time series" table will not be created.

      timeSeriesData (optional): A list of lists, each of which contains 1 row of data to be added
      to the right table.

      This method automatically adds, as the first column of the run-summary table, a column
      labeled "Number of Time Points", which contains the number of time points in the timeseries
      that is associated with the run.

      Note: Call this method once per run, or row of data to be added to the left table.
      This method "does the right thing" if per-run column labels are added, removed, and/or
      reordered between calls to the method. However, currently, it does not handle the removal
      of time series labels (except from the end of the list) and it does not handle reordering of
      time series labels.
    */
    exportData: function(perRunLabels, perRunData, timeSeriesLabels, timeSeriesData) {
      timeSeriesLabels = timeSeriesLabels || [];

      var label,
          value,
          position,
          perRunColumnLabels = [],
          perRunColumnValues = [],
          timeSeriesColumnLabels = [],
          shouldExportTimeSeries,
          parentTableName,
          childTableName,
          i;

      // Extract metadata in the forms needed for export, ie values need to be an array of values,
      // labels need to be an array of {name: label} objects.
      // Furthermore note that during a DG session, the value for a given label needs to be in the
      // same position in the array every time the DG collection is 'created' (or reopened as the
      // case may be.)

      for (i = 0; i < perRunData.length; i++) {
        label = perRunLabels[i];
        value = perRunData[i];

        if ( this.perRunColumnLabelPositions[label] == null ) {
          this.perRunColumnLabelPositions[label] = this.perRunColumnLabelCount++;
        }
        position = this.perRunColumnLabelPositions[label];

        if (i === 0) {
          perRunColumnLabels[position] = { name: label, formula: "caseIndex" , type:"nominal"};
          perRunColumnValues[position] = null;
        } else {
          perRunColumnLabels[position] = { name: label };
          perRunColumnValues[position] = value;
        }
      }

      // Extract list of data column labels into form needed for export (needs to be an array of
      // name: label objects)
      for (i = 0; i < timeSeriesLabels.length; i++) {
        timeSeriesColumnLabels.push({ name: timeSeriesLabels[i] });
      }

      shouldExportTimeSeries = timeSeriesLabels.length > 0;

      // Export.

      if (shouldExportTimeSeries) {
        parentTableName = this.parentTableLabels.pluralCase;
        childTableName = this.childTableLabels.pluralCase;
      } else {
        parentTableName = this.singleTableLabels.pluralCase;
      }

      var collections = [{
        name: parentTableName,
        attrs: perRunColumnLabels,
        childAttrName: 'runs',
        labels: shouldExportTimeSeries ? this.parentTableLabels : this.singleTableLabels,
        collapseChildren: true
      }];

      if (shouldExportTimeSeries) {
        collections.push({
          name: childTableName,
          attrs: timeSeriesColumnLabels,
          labels: this.childTableLabels
        });
      }

      // Step 1. Tell DG we're a "game".
      this.doCommand('initGame', {
        name: this.gameName,
        collections: collections
      });

      // Step 4. Open a row in the parent table. This will contain the individual time series
      // readings as children.
      this.doCommand('openCase', {
        collection: parentTableName,
        values: perRunColumnValues
      }, function(parentCase) {

        // Step 5. Create rows in the child table for each data point. Using 'createCases' we can
        // do this inline, so we don't need to call openCase, closeCase for each row.
        if (shouldExportTimeSeries) {
          this.doCommand('createCases', {
            collection: childTableName,
            values: timeSeriesData,
            parent: parentCase.caseID
          });
        }

        // Step 6. Close the case.
        this.doCommand('closeCase', {
          collection: parentTableName,
          caseID: parentCase.caseID
        });
      }.bind(this));
    },

    /**
      Call this to cause DataGames to open the 'case table" containing the all the data exported by
      exportData() so far.
    */
    openTable: function() {
      this.doCommand('createComponent', {
        type: 'DG.TableView',
        log: false
      });
    },

    /**
      Call any time to log an event to DataGames
    */
    logAction: function(logString) {
      this.doCommand('logAction', {
        formatStr: logString
      });
    }
  };

  return dgExporter;
});

/*global define, $ */

define('common/controllers/basic-dialog',[],function () {

  var defOptions = {
    autoOpen: false,
    dialogClass: "interactive-dialog",
    width: "80%"
  };

  // E.g. "interactive-dialog" -> "InteractiveDialog".
  function titleizeClass(className) {
    return className.split('-').map(function(s) { return s[0].toUpperCase() + s.slice(1); }).join('');
  }

  /**
   * Simple wrapper around the jQuery UI Dialog,
   * which provides useful defaults and simple interface.
   *
   * @constructor
   * @param {Object} options jQuery UI Dialog options.
   */
  function BasicDialog(options, i18n) {
    /**
     * Basic dialog elements.
     * @type {jQuery}
     */
    var title = options.title || '';
    var id = options.id || '';

    this.$element = $('<div id="'+id+'" title="'+title+'">');
    // Create jQuery UI Dialog.
    options = $.extend({closeText: i18n.t("dialog.close_tooltip")}, defOptions, options)
    this.$element.dialog(options);
    this._eventNamePrefix = titleizeClass(options.dialogClass);
  }

  /**
   * Opens the dialog.
   */
  BasicDialog.prototype.open = function() {
    // Limit height of the content to 50% window height.
    this.$content.css("max-height", ($(window).height() * 0.5) + "px");
    this.$element.dialog("open");
  };

  /**
   * Closes the dialog.
   */
  BasicDialog.prototype.close = function() {
    this.$element.dialog("close");
  };

  /**
   * Sets jQuery UI Dialog option.
   *
   * @param {string} key
   * @param {Object} value
   */
  BasicDialog.prototype.set = function(key, value) {
    this.$element.dialog("option", key, value);
  };

  /**
   * Sets content of the dialog.
   *
   * @param {jQuery|DOM|string} content Any value that can be accepted by the jQuery.append.
   */
  BasicDialog.prototype.setContent = function (content) {
    // Wrap `content` in <div> so we can support raw HTML passed as a string.
    this.$content = $('<div>').append(content);
    this.$element.empty();
    // Not very pretty, but probably the simplest and most reliable way to
    // disable autofocus in jQuery UI dialogs. See:
    // http://jqueryui.com/upgrade-guide/1.10/#added-ability-to-specify-which-element-to-focus-on-open
    this.$element.append('<input type="hidden" autofocus="autofocus" />');
    this.$element.append(this.$content);
  };

  /**
   * Enables logging.
   *
   * @param {function} logFunc function that accepts action name and data
   */
  BasicDialog.prototype.enableLogging = function (logFunc) {
    var openTime = null;
    var eventNamePrefix = this._eventNamePrefix;
    this.$element.off('.logging');
    this.$element.on('dialogopen.logging', function () {
      logFunc(eventNamePrefix + 'Opened');
      openTime = Date.now();
    });
    this.$element.on('dialogclose.logging', function () {
      logFunc(eventNamePrefix + 'Closed', {wasOpenFor: (Date.now() - openTime) / 1000});
    });
  };

  return BasicDialog;
});

/*global define*/
/*jslint boss: true*/

define('common/controllers/export-controller',['require','import-export/dg-exporter','common/controllers/basic-dialog','common/dispatch-support','underscore'],function (require) {

  var dgExporter = require('import-export/dg-exporter');
  var BasicDialog = require('common/controllers/basic-dialog');
  var DispatchSupport = require('common/dispatch-support');
  var _ = require('underscore');

  function modalAlert(title, message, buttons, i18n) {
    var dialog = new BasicDialog({
      width: "60%",
      modal: true,
      id: 'exporter-modal-alert',
      title: title,
      buttons: buttons
    }, i18n);

    dialog.setContent(message);
    dialog.open();
  }

  // Handles CODAP data export. Also, defines a few new log events which are sent to parent when export is possible.
  // In such case it's using generic logAction method, but in practice LogController will talk to CODAP too.
  function ExportController(interactivesController) {
    var dispatch = new DispatchSupport(),
        spec,
        perRun,
        perTick,
        selectionComponents,
        perTickValues,
        controller,
        model,

        // used to compare initial parameters to parameters at export
        initialPerRunData,

        // Are we waiting for timeseries data, or just sending "parent level" data to one table?
        shouldExportTimeSeries = false,

        // Is the exporter set up and ready to allow the model to export data? The difference
        // between this and canExportData is that canExportData merely exposes the fact that we
        // are embedded in CODAP or some other target for our exported data. However
        // modelCanExportData indicates that the export controller has been initialized and is
        // attached to the current model.
        modelCanExportData = false,

        // Has exportData been called for this model since the last load or reset event?
        isUnexportedDataPresent = false,

        // Whether user needs to confirm (ok/cancel) if discarding data. Set by "don't ask again"
        // checkbox when discarding data the first time`
        askAboutDataDiscard = true,

        isInitialized = false;

    function getDataPoint() {
      var ret = [], i, len;

      for (i = 0, len = perTick.length; i < len; i++) {
        ret.push(model.get(perTick[i]));
      }
      return ret;
    }

    function resetData() {
      perTickValues = [getDataPoint()];
    }

    function appendDataPoint() {
      perTickValues.push(getDataPoint());
      // indicate that latest data hasn't been exported
      isUnexportedDataPresent = true;
    }

    function removeDataAfterStepPointer() {
      // Account for initial data, which corresponds to stepCounter == 0
      perTickValues.length = model.stepCounter() + 1;
    }

    function logAction(action, state) {
      var data;
      if (state) {
        // Convert list of labels and values into plain JS object.
        data = {};
        for (var i = 0; i < state.labels.length; i++) {
          data[state.labels[i]] = state.values[i];
        }
      }
      interactivesController.logAction(action, data);
    }

    function shouldHandleDataDiscard() {
      // If there's no unexported data, or we're not in the DG environment, never mind.
      return ExportController.canExportData() &&
        askAboutDataDiscard &&
        shouldExportTimeSeries &&
        isUnexportedDataPresent;
    }

    // Called when a model is about to be reset or reloaded, there is unexported data, and the user
    // has not asked to ignore data discard
    function handleDataDiscard(resetRequest) {

      // Yuck (UI in the controller layer), but here we go.
      modalAlert(
        "Discard data?",
        "<p>Pressing New Run without pressing Analyze Data will discard the current data. " +
        "Set up a new run without saving the data first?</p>" +
          "<input type='checkbox' id='dont-ask' name='dont-ask'></input>"+
          "<label for='dont-ask'>Don't show this message again</label>" , [
          {
            id: 'button-cancel',
            text: "Go back",
            click: function() {
              askAboutDataDiscard = ! $('#dont-ask').is(':checked');
              $(this).remove();
              resetRequest.cancel();
            }
          },
          {
            id: 'button-reset',
            text: "Discard the data",
            click: function() {
              logAction('DiscardedData', getCurrentPerRunData());
              askAboutDataDiscard = ! $('#dont-ask').is(':checked');
              $(this).remove();
              resetRequest.proceed();
            }
          }
        ],
        interactivesController.i18n
      );
    }

    // Called when exporting data; detects changes to per-run parameters since the model's initial
    // 'play' event and returns in a changelist form ready to be exported to the DG log.
    function getChangedParameterValues() {
      if (!initialPerRunData) {
        return false;
      }

      var currentPerRunData = getCurrentPerRunData();
      var changesList = { values: [], labels: [] };
      var anyChanged = false;

      currentPerRunData.values.forEach(function(currentValue, i) {
        var initialValue = initialPerRunData.values[i];
        var parameter = currentPerRunData.labels[i];
        var changed = initialValue !== currentValue;

        changesList.labels.push(parameter + ' changed?');
        changesList.values.push(changed);
        anyChanged = anyChanged || changed;

        changesList.labels.push(parameter + ' (start of run)');
        changesList.values.push(initialValue);

        changesList.labels.push(parameter + ' (sent to CODAP)');
        changesList.values.push(currentValue);
      });

      return anyChanged ? changesList : false;
    }

    function registerModelListeners() {
      // Namespace listeners to '.exportController' so we can eventually remove them all at once
      model.on('tick.exportController', appendDataPoint);
      model.on('reset.exportController', resetData);
      model.on('play.exportController', function() {
        removeDataAfterStepPointer();
        // Save the per-run parameters we see now -- we'll log if a user changes any parameters
        // before exporting the data
        if (!initialPerRunData) {
          initialPerRunData = getCurrentPerRunData();
        }
      });
      model.on('invalidation.exportController', removeDataAfterStepPointer);
    }

    function willResetModelHandler(modelToBeReset, resetRequest) {

      if (modelToBeReset !== model || !shouldHandleDataDiscard()) {
        // false lets interactives controller know it should not wait for a response from us
        return false;
      }

      // There's unexported data and we're supposed to ask the user about it.

      // put up the message and aynchronously wait for a response indicating whether or not to
      // continue with the reset.
      handleDataDiscard(resetRequest);

      // Let interactives controller know it should await our response
      return true;
    }

    function registerInteractiveListeners() {
      interactivesController.on('modelLoaded.exportController', function(cause) {
        handleModelInitialization('modelLoaded', cause);
      });

      interactivesController.on('modelReset.exportController', function(cause) {
        handleModelInitialization('modelReset', cause);
      });
      // Currently there is no need to namespace these particular listeners, because interactive
      // controller uses a *special* on() method that doesn't just delegate to d3.dispatch; in fact
      // it doesn't understand namespacing!
      interactivesController.on('willResetModel', willResetModelHandler);
    }

    function handleModelInitialization(eventName, cause) {

      function handleModelLoaded() {
        model = interactivesController.getModel();

        function propertyExists(p) {
          // (Don't write 'model.properties.hasOwnProperty' because we should eventually *remove*
          // hasOwnProperty and other Object.prototype methods from model.properties' prototype
          // chain -- as it stands there appears to be a model property called 'hasOwnPropety'.)
          return Object.prototype.hasOwnProperty.call(model.properties, p);
        }

        perRun  = (spec.perRun || []).filter(propertyExists);
        if (spec.perTick == null || spec.perTick.length === 0) {
          shouldExportTimeSeries = false;
          perTick = [];
        } else {
          shouldExportTimeSeries = true;
          perTick = ['displayTime'].concat(spec.perTick.filter(propertyExists));
        }

        resetData();
        registerModelListeners();

        if (cause === 'new-run') {
          logAction("SetUpNewRun");
        }

        initialPerRunData = null;
        isUnexportedDataPresent = false;
        if (controller.canExportData()) {
          modelCanExportData = true;
          dispatch.modelCanExportData();
        }
      }

      modelCanExportData = false;

      // Don't accumulate data or logs until we know we know there is somewhere to send the data.
      // (Note that CODAP, if present, will announce itself before the model can be started by the
      // user, so there should not be data loss.)
      if (controller.canExportData()) {
        handleModelLoaded();
      } else {
        controller.on('canExportData.export-controller', handleModelLoaded);
      }
    }

    function getCurrentPerRunData() {
      var state = {};

      state.labels = [];
      state.values = [];

      for (var i = 0; i < perRun.length; i++) {
        state.labels[i] = getLabelForProperty(perRun[i]);
        state.values[i] = model.get(perRun[i]);
      }
      return state;
    }

    function getLabelForProperty(property) {
      var desc  = model.getPropertyDescription(property),
          label = desc && desc.getLabel(),
          units = desc && desc.getUnitAbbreviation(),
          ret   = "";

      if (label && label.length > 0) {
        ret += label;
      } else {
        ret += property;
      }

      if (units && units.length > 0) {
        ret += " (";
        ret += units;
        ret += ")";
      }
      return ret;
    }

    controller = {

      // This just indicates the presence or absence of a technical means to export data (i.e.,
      // whether or not there is CODAP or some other data sink is present and listening for data)
      canExportData: function() {
        return ExportController.canExportData();
      },

      modelCanExportData: function() {
        return modelCanExportData;
      },

      // This indicates whether the default UI should allow data export. (This is advisory; custom
      // scripts can choose to call exportData() while ignoring this value)
      // Currently,
      //   * if the interactive's exports spec omits timeseries data, data can always be exported
      //   * if the interactive's exports spec includes timeseries data, the model must have been
      //     run, and it must now be stopped.
      dataAreAvailableForExport: function() {
        return ! shouldExportTimeSeries || (model.properties.hasPlayed && model.isStopped() && isUnexportedDataPresent);
      },

      isUnexportedDataPresent: function() {
        return isUnexportedDataPresent;
      },

      init: function(_spec) {
        spec = _spec;
        selectionComponents = (spec.selectionComponents || []).slice();

        isInitialized = true;
      },

      selectedData: function() {
        var i, component, domain, min = Infinity, max = -Infinity, outputData = [];

        if ( ! isInitialized ) {
          throw new Error("ExportController: selectData() was called before controller was initialized.");
        }

        for (i = 0; i < selectionComponents.length; i++) {
          component = interactivesController.getComponent(selectionComponents[i]);
          if (component && component.selectionDomain) {
            domain = component.selectionDomain();
            if (domain !== null && domain.length === 2) {
              if (min > domain[0]) {
                min = domain[0];
              }
              if (max < domain[1]) {
                max = domain[1];
              }
            }
          }
        }

        if (min < Infinity || max > -Infinity) {
          // filter the data to only that data which fails within this domain
          outputData = perTickValues.filter(function(point) {
            return point[0] > min && point[0] < max;
          });
        } else {
          outputData = perTickValues;
        }
        return outputData;
      },

      exportData: function() {
        var perRunPropertyLabels = [],
            perRunPropertyValues = [],
            perTickLabels = [],
            changedParameters,
            i;

        if ( ! isInitialized ) {
          throw new Error("ExportController: exportData() was called before controller was initialized.");
        }

        logAction("ExportedModel", getCurrentPerRunData());

        changedParameters = getChangedParameterValues();

        // Create a separate log event for the act of having changed parameters
        if (changedParameters) {
          logAction("ParameterChangeBetweenStartAndExport", changedParameters);
        }

        perRunPropertyLabels[0] = "Row";
        perRunPropertyValues[0] = null;

        for (i = 0; i < perRun.length; i++) {
          perRunPropertyLabels[i+1] = getLabelForProperty(perRun[i]);
          perRunPropertyValues[i+1] = model.get(perRun[i]);
        }

        for (i = 0; i < perTick.length; i++) {
          perTickLabels[i] = getLabelForProperty(perTick[i]);
        }

        dgExporter.exportData(perRunPropertyLabels, perRunPropertyValues, perTickLabels, this.selectedData());
        dgExporter.openTable();

        // all data was just exported
        isUnexportedDataPresent = false;
      }
    };

    // Setup

    dgExporter.init();

    // Issue an 'canExportData' event when canExportData() flips from false to true.
    // Issue 'modelCanExportData' when modelCanExportData() flips
    dispatch.mixInto(controller);
    dispatch.addEventTypes('canExportData', 'modelCanExportData');

    // Make sure we emit event if canExportData becomes true. Assume codap connects only once.
    dgExporter.codapDidConnect = function() {
      if ( ExportController.canExportData() ) {
        dispatch.canExportData();
      }
    };

    registerInteractiveListeners();

    return controller;
  }

  // "Class method" (want to be able to call this before instantiating)
  ExportController.canExportData = function() {
    return dgExporter.canExportData();
  };

  return ExportController;
});
/*global define*/
/*jslint boss: true*/

define('common/controllers/log-controller',['require','iframe-phone','import-export/dg-exporter'],function (require) {
  var iframePhone = require('iframe-phone');
  var dgExporter = require('import-export/dg-exporter');

  // Handles logging of events to LARA or CODAP.
  function LogController(args) {
    var config = args.config;
    this.enabled = config.enabled;
    // Use either provided list of properties bound to components (widgets) or list specified explicitly in config.
    this._properties = config.properties === 'boundToComponents' ? args.boundProperties : config.properties;
    this._interactivesController = args.interactivesController;
    this._model = null;
    
    // Two possible parents that listen to our logs - LARA or CODAP.
    this._phone = iframePhone.getIFrameEndpoint();
    // IFrameEndpoint is a singleton and probably has been already initialized by ParentMessageAPI,
    // but do it again just in case (so we don't depend on ParentMessageAPI).
    this._phone.initialize();
    dgExporter.init();
    
    this._interactivesController.on('modelLoaded.logController', this._modelLoadedHandler.bind(this));
    this._interactivesController.on('interactiveWillReload.logController', this._interactiveWillReloadHandler.bind(this));

    this._setupComponents(args.componentByID, config.components);
    this._enableLoggingIn(args.additionalComponents);
  }

  LogController.prototype.logAction = function (action, data) {
    if (!this.enabled) return;

    if (dgExporter.isEmbeddedInCODAP()) {
      this._logToCODAP(action, data);
    } else {
      this._genericLog(action, data);
    }
  };

  LogController.prototype._genericLog = function (action, data) {
    this._phone.post('log', {action: action, data: data});
  };

  LogController.prototype._logToCODAP = function (action, data) {
    var logString = action;
    if (data) {
      logString += ': ' + JSON.stringify(data);
    }
    dgExporter.logAction(logString);
  };

  LogController.prototype._setupComponents = function (componentByID, enabledComponents) {
    if (enabledComponents === 'none' || enabledComponents === []) return;
    if (enabledComponents === 'all') {
      enabledComponents = Object.keys(componentByID);
    }
    var componentsList = [];
    enabledComponents.forEach(function (compID) {
      componentsList.push(componentByID[compID]);
    });
    this._enableLoggingIn(componentsList);
  };

  LogController.prototype._enableLoggingIn = function (componentsList) {
    var logFunction = this.logAction.bind(this);
    componentsList.forEach(function (comp) {
      // Enable logging and provide function that component can use to log its own events.
      if (comp && comp.enableLogging) {
        comp.enableLogging(logFunction);
      }
    });
  };

  LogController.prototype._interactiveWillReloadHandler = function () {
    // We can log ReloadedInteractive before it actually happens, it's just simpler.
    this.logAction('ReloadedInteractive', this._getProperties());
  };

  LogController.prototype._modelLoadedHandler = function (cause) {
    this._model = this._interactivesController.getModel();

    this._model.on('log.logController', function (action, data) {
      // Models can log custom events too using dispatch. Just pass them to the parent.
      this.logAction(action, data);
    }.bind(this));

    this._model.on('play.logController', function () {
      this.logAction('StartedModel', this._getProperties());
    }.bind(this));

    this._model.on('stop.logController', function () {
      this.logAction('StoppedModel', this._getProperties());
    }.bind(this));

    this._model.on('willReset.logController', function() {
      // We can log ReloadedModel before it actually happens, it's just simpler.
      // Note that when user cancels reload (e.g. inside CODAP), this even is not emitted.
      this.logAction('ReloadedModel', this._getProperties());
    }.bind(this));
  };


  LogController.prototype._getProperties = function () {
    var model = this._model;
    var propData = {};
    function getLabelForProperty(property) {
      var desc = model.getPropertyDescription(property);
      var label = desc && desc.getLabel();
      var units = desc && desc.getUnitAbbreviation();
      var ret   = "";
      if (label && label.length > 0) {
        ret += label;
      } else {
        ret += property;
      }
      if (units && units.length > 0) {
        ret += " (" + units + ")";
      }
      return ret;
    }

    this._properties.forEach(function (prop) {
      propData[getLabelForProperty(prop)] = model.get(prop)
    });
    return propData;
  };

  return LogController;
});

/*global d3, $, define */
/*jshint loopfunc: true */

define('common/controllers/scripting-api',['require','common/alert'],function (require) {

  var alert = require('common/alert');
  var namespaceCount = 0;

  // This object is the outer context in which each script function is executed. This prevents at
  // least inadvertent reliance by the script on unintentinally exposed globals. Note that this
  // object is shared by the all instances of functions created in Scripting API context
  // (see makeFunctionInScriptContext).
  var shadowedGlobals = {};

  function errorForKey(key) {
    return function() {
      throw new ReferenceError(key + " is not defined");
    };
  }

  // Make shadowedGlobals contain keys for all globals (properties of 'window').
  // Also make set and get of any such property throw a ReferenceError exactly like
  // reading or writing an undeclared variable in strict mode.
  function setShadowedGlobals() {
    var keys = Object.getOwnPropertyNames(window),
        key,
        i,
        len,
        err;

    for (i = 0, len = keys.length; i < len; i++) {
      key = keys[i];
      if (!shadowedGlobals.hasOwnProperty(key)) {
        err = errorForKey(key);

        Object.defineProperty(shadowedGlobals, key, {
          set: err,
          get: err
        });
      }
    }
  }

  //
  // Define the scripting API used by 'action' scripts on interactive elements.
  //
  // The properties of the object below will be exposed to the interactive's
  // 'action' scripts as if they were local vars. All other names (including
  // all globals, but exluding Javascript builtins) will be unavailable in the
  // script context; and scripts are run in strict mode so they don't
  // accidentally expose or read globals.
  //
  return function ScriptingAPI (interactivesController, model) {

    // Note. Normally, scripting API methods should not create event listeners to be added to the
    // interactivesController, because doing so from an onLoad script results in adding a new event
    // listener per model load or reload. The interactivesController has no mechanism for
    // associating listeners with a particular model and removing them after load; that semantics is
    // handled by adding listeners directly to a model.

    // Ensure that we don't overwrite model.reset observers.
    // MUST. FIX. EVENT. OBSERVING. to get rid of this ridiculous unique id requirement!
    function onModelReset(callback) {
      model.on('reset.common-scripting-api-' + (namespaceCount++), callback);
    }

    var controller = {

      api: (function() {

        function isInteger(n) {
          // Exploits the facts that (1) NaN !== NaN, and (2) parseInt(Infinity, 10) is NaN
          return typeof n === "number" && (parseFloat(n) === parseInt(n, 10));
        }

        function isArray(obj) {
          return typeof obj === 'object' && obj.slice === Array.prototype.slice;
        }

        /** return a number randomly chosen between 0..max */
        function randomFloat(max) {
          if (max) {
            return Math.random() * max;
          } else {
            return Math.random();
          }
        }

        /** return an integer randomly chosen from the set of integers 0..n-1 */
        function randomInteger(n) {
          return Math.floor(Math.random() * n);
        }

        function swapElementsOfArray(array, i, j) {
          var tmp = array[i];
          array[i] = array[j];
          array[j] = tmp;
        }

        /** Return an array of n randomly chosen members of the set of integers 0..N-1 */
        function choose(n, N) {
          var values = [],
              i;

          for (i = 0; i < N; i++) { values[i] = i; }

          for (i = 0; i < n; i++) {
            swapElementsOfArray(values, i, i + randomInteger(N-i));
          }
          values.length = n;

          return values;
        }

        /* Send a tracking event to Google Analytics */
        function trackEvent(category, action, label) {
          var googleAnalytics;

          if (typeof _gaq === 'undefined'){
            // console.error("Google Analytics not defined, Can not send trackEvent");
            return;
          }
          googleAnalytics = _gaq;
          if (!category) {
            category = "Interactive";
          }
          // console.log("Sending a track page event Google Analytics (category:action:label):");
          // console.log("(" + category + ":"  + action + ":" + label + ")");
          googleAnalytics.push(['_trackEvent', category, action, label]);
        }

        return {
          isInteger: isInteger,
          isArray: isArray,
          randomInteger: randomInteger,
          randomFloat: randomFloat,
          swapElementsOfArray: swapElementsOfArray,
          choose: choose,

          deg2rad: Math.PI/180,
          rad2deg: 180/Math.PI,

          trackEvent: trackEvent,

          format: d3.format,

          get: function get() {
            return model.get.apply(model, arguments);
          },

          set: function set() {
            return model.set.apply(model, arguments);
          },

          freeze: function freeze() {
            return model.freeze.apply(model, arguments);
          },

          unfreeze: function unfreeze() {
            return model.unfreeze.apply(model, arguments);
          },

          /**
           * Logs custom event specified by author. Note that logging needs to be enabled in interactive JSON!
           * It means "logging": {"enabled": true} needs to be specified.
           * Log message will be sent to parent window, so parent window needs to handle it.
           * Currently, only LARA does it and sends logs to CC Log Manager App.
           *
           * @param {string} actionName
           * @param {object} data Hash of key-values that should be logged.
           */
          logAction: function logAction(actionName, data) {
            interactivesController.logAction(actionName, data);
          },

          // optional 'parameters' list of values to pass into the loaded model
          //
          // TODO remove optional parameter list when interactives have parameters that
          //      exist beyond model loading
          loadModel: function loadModel(modelId, parameters) {
            model.stop();
            interactivesController.loadModel(modelId, null, parameters);
          },

          getLoadedModelId: function getLoadedModel() {
            return interactivesController.getLoadedModelId();
          },

          /**
            Observe property `propertyName` on the model, and perform `action` when it changes.
            Pass property value to action.
          */
          onPropertyChange: function onPropertyChange(propertyName, action) {
            model.addPropertiesListener([propertyName], function() {
              action( model.get(propertyName) );
            });
          },

          /**
           * Performs a user-defined script at any given time.
           *
           * callAt(t, ...) guarantees that script will be executed, but not necessarily
           * at exactly chosen time (as this can be impossible due to simulation settings).
           * User scripts cannot interrupt the model "tick", the most inner integration loop.
           * e.g. callAt(23, ...) in MD2D model context will be executed at time 50,
           * if timeStepsPerTick = 50 and timeStep = 1.
           *
           * callAt action will occur when the model reaches the specified time and the simulation
           * is running at the moment. Note that just stepping forward and backward in time won't
           * trigger action again, but if you step back and start the simulation, then it will.
           *
           * @param  {number} time     Time defined in model native time unit (e.g. fs for MD2D).
           * @param  {function} action Function containing user-defined script.
           */
          callAt: function callAt(time, action) {
            function checkTime() {
              if (model.properties.time >= time) {
                action();
                stopChecking();
              }
            }

            function startChecking() {
              // addObserver(key, callback) is idempotent
              model.addObserver('time', checkTime);
            }

            function stopChecking() {
              model.removeObserver('time', checkTime);
            }

            function onStartHandler() {
              // This callback handles situation in which user moved back in time using tick
              // history and clicked play again. Setup checking again. Note that startChecking()
              // is idempotent so we can call it many times.
              if (model.properties.time < time) {
                startChecking();
              }
            }

            onModelReset(startChecking);
            this.onStart(onStartHandler);
            startChecking();
          },

          /**
           * Performs a user-defined script repeatedly, with a fixed time delay
           * between each call.
           *
           * callEvery(t, ...) guarantees that script will be executed *correct number of times*,
           * but not necessarily at exactly chosen intervals (as this can be impossible due to
           * simulation settings). User scripts cannot interrupt the model "tick", the most
           * inner integration loop.
           * e.g. callEvery(23, ...) in MD2D model context will be executed *twice* at time 50,
           * if timeStepsPerTick = 50 and timeStep = 1.
           *
           * callEvery action for time N * interval (for any integer N >= 1) will only be called
           * when the model time exceeds N * interval time. Note that just stepping forward and
           * backward in time won't trigger action again, but if you step back and start the
           * simulation, then it will.
           *
           * @param {number}   interval Interval on how often to execute the script,
           *                            defined in model native time unit (e.g. fs for MD2D).
           * @param {function} action   Function containing user-defined script.
           */
          callEvery: function callEvery(interval, action) {
            var lastCall = 0;

            function checkTime() {
              while (model.properties.time - lastCall >= interval) {
                action();
                lastCall += interval;
              }
            }

            function resetState() {
              lastCall = 0;
            }

            function onStartHandler() {
              // This callback handles situation in which user moved back in time using tick
              // history and clicked play again.
              while (lastCall > model.properties.time) {
                lastCall -= interval;
              }
            }

            model.addObserver('time', checkTime);
            onModelReset(resetState);
            this.onStart(onStartHandler);
          },

          /**
           * Sets a custom click handler for objects of a given type.
           * Basic type which is always supported is "background". It is empty
           * area of a model. Various models can support different clickable
           * types. Please see the model documentation to check what
           * other object types are supported.
           *
           * Behind the scenes this functions uses class selector. So you can
           * also inspect SVG image and check what is class of interesting
           * object and try to use it.
           *
           * MD2D specific notes:
           * Supported types: "background", "atom", "obstacle", "image", "textBox".
           * TODO: move it to MD2D related docs in the future.
           *
           * @param {string}   type    Name of the type of clickable objects.
           * @param {Function} handler Custom click handler. It will be called
           *                           when object is clicked with (x, y, d, i) arguments:
           *                             x - x coordinate in model units,
           *                             y - y coordinate in model units,
           *                             d - data associated with a given object (can be undefined!),
           *                             i - ID of clicked object (usually its value makes sense if d is defined).
           */
          onClick: function onClick(type, handler) {
            // Append '.' to make API simpler.
            // So authors can just specify onClick("atom", ...) instead of class selectors.
            interactivesController.modelController.modelContainer.setClickHandler("." + type, handler);
          },

          /**
           * Sets a custom drag handler for objects of a given type.
           * Drag handler will be executed after position of an object is updated due to user
           * dragging action, so custom handler can affect it (e.g. limit to only one axis), e.g:
           *
           *   onDrag("atom", function (x, y, i, d) {
           *     setAtomProperties(i, {y: 2});
           *   });
           *
           * MD2D specific notes:
           * only "atom" type is supported.
           *
           * @param {string}   type    Name of the type of draggable objects.
           * @param {Function} handler Custom drag handler. It will be called
           *                           when object is dragged with (x, y, d, i) arguments:
           *                             x - x coordinate in model units,
           *                             y - y coordinate in model units,
           *                             d - data associated with a given object (can be undefined!),
           *                             i - ID of an object (usually its value makes sense if d is defined).
           */
          onDrag: function onDrag(type, handler) {
            interactivesController.modelController.modelContainer.setDragHandler(type, handler);
          },

          /**
           * Sets custom select handler. It enables select action and lets author provide custom handler
           * which is executed when select action is finished. The area of selection is passed to handler
           * as arguments. It is defined by rectangle - its lower left corner coordinates, width and height.
           *
           * @param {Function} handler Custom select handler. It will be called
           *                           when select action is finished with (x, y, w, h) arguments:
           *                             x - x coordinate of lower left selection corner (in model units),
           *                             y - y coordinate of lower left selection corner (in model units),
           *                             width  - width of selection rectangle (in model units),
           *                             height - height of selection rectangle (in model units).
           */
          onSelect: function onSelect(handler) {
            interactivesController.modelController.modelContainer.setSelectHandler(handler);
          },

          setComponentDisabled: function setComponentDisabled(compID, v) {
            interactivesController.getComponent(compID).setDisabled(v);
          },

          /**
            Clears data set completely.
           */
          clearDataSet: function clearDataSet(name) {
            interactivesController.getDataSet(name).clearData();
          },

          /**
            Resets data sat to its initial data. When initial data is not provided, clears data
            set (in such case this function behaves exactly like .clearDataSet()).
           */
          resetDataSet: function resetDataSet(name) {
            interactivesController.getDataSet(name).resetData();
          },

          resetDataSetProperties: function resetDataSetProperties(name, props) {
            interactivesController.getDataSet(name).resetProperties(props);
          },

          /**
            Used when manually adding points to a graph or a table.
            Normally the graph or table property streamDataFromModel should be false
            when using this function.
          */
          appendDataPropertiesToComponent: function appendDataPropertiesToComponent(compID) {
            var comp = interactivesController.getComponent(compID);
            if (comp !== undefined) {
              comp.appendDataPropertiesToComponent();
            }
          },

          /**
            Change attributes of an existing component.

            WARNING: the current implementation of this function is very limited. Despite its
                     generic name, it only lets you change graph's attributes or button's label.
          */
          setComponentAttributes: function setComponentAttributes(componentID, opts) {
            var comp = interactivesController.getComponent(componentID);

            if (!comp) {
              throw new Error("Component " + componentID + " not found.");
            }
            if (typeof(comp.setAttributes) !== "function") {
              throw new Error("Component " + componentID + " does not support dynamic attributes change.");
            }
            comp.setAttributes(opts);
          },

          /**
            Set the ranges of graph component to match the ranges of the properties it is graphing.
          */
          syncAxisRangesToPropertyRanges: function syncAxisRangesToPropertyRanges(componentID) {
            var component = interactivesController.getComponent(componentID);

            if (!component) {
              throw new Error("Component " + componentID + " not found.");
            }
            if (!component.syncAxisRangesToPropertyRanges) {
              throw new Error("Component " + componentID + " does not support syncAxisRangesToPropertyRanges.");
            }

            component.syncAxisRangesToPropertyRanges();
          },

          scrollXAxisToZero: function scrollXAxisToZero(componentID) {
            var component = interactivesController.getComponent(componentID);

            if (!component) {
              throw new Error("Component " + componentID + " not found.");
            }
            if (!component.scrollXAxisToZero) {
              throw new Error("Component " + componentID + " does not support scrollXAxisToZero.");
            }

            component.scrollXAxisToZero();
          },

          resetGraphSelection: function resetGraphSelectionDomain(componentID) {
            var component = interactivesController.getComponent(componentID);

            if (!component) {
              throw new Error("Component " + componentID + " not found.");
            }
            if (!component.selectionDomain) {
              throw new Error("Component " + componentID + " does not support selectionDomain.");
            }
            if (!component.selectionEnabled) {
              throw new Error("Component " + componentID + " does not support selectionEnabled.");
            }

            component.selectionDomain(null);
            component.selectionEnabled(false);
          },

          addAnnotation: function addAnnotation(componentID, annotation) {
            var component = interactivesController.getComponent(componentID);

            if (!component) {
              throw new Error("Component " + componentID + " not found.");
            }
            if (!component.addAnnotation) {
              throw new Error("Component " + componentID + " does not support addAnnotation.");
            }

            component.addAnnotation(annotation);
          },

          resetAnnotations: function resetAnnotations(componentID) {
            var component = interactivesController.getComponent(componentID);

            if (!component) {
              throw new Error("Component " + componentID + " not found.");
            }
            if (!component.resetAnnotations) {
              throw new Error("Component " + componentID + " does not support resetAnnotations.");
            }

            component.resetAnnotations();
          },

          start: function start() {
            model.start();
            trackEvent('Interactive', "Start", "Starting interactive: " + interactivesController.get('title') );
          },

          onStart: function onStart(handler) {
            model.on("play.custom-script" + (namespaceCount++), handler);
          },

          stop: function stop() {
            model.stop();
          },

          onStop: function onStop(handler) {
            model.on("stop.custom-script", handler);
          },

          /**
           * Reload the model. The interactives controller will emit a 'willResetModel'.
           * The willResetModel observers can ask to wait for asynchronous confirmation before
           * the model is actually reloaded.
           * @param  {object} options hash of options, supported properties:
           *                         * propertiesToRetain - a list of properties to save before
           *                           the model reload and restore after reload.
           *                         * cause - cause of the reload action, it can be e.g. "reload"
           *                           or "new-run". It will be passed to "modelLoaded" event handlers.
           */
          reloadModel: function reloadModel(options) {
            interactivesController.reloadModel(options);
          },

          /**
           * Reload the interactive. The interactives controller will emit a 'willResetModel',
           * as obviously the interactive reload causes the model to be restored to its initial
           * state too. The willResetModel observers can ask to wait for asynchronous confirmation
           * before the interactive and model is actually reloaded.
           */
          reloadInteractive: function reloadInteractive() {
            interactivesController.reloadInteractive();
          },

          stepForward: function stepForward() {
            model.stepForward();
            if (!model.isNewStep()) {
              interactivesController.updateModelView();
            }
          },

          stepBack: function stepBack() {
            model.stepBack();
            interactivesController.updateModelView();
          },

          tick: function tick() {
            model.tick();
          },

          isStopped: function isStopped() {
            return model.isStopped();
          },

          getTime: function getTime() {
            return model.get('time');
          },

          /**
           * Returns number of frames per second.
           * @return {number} frames per second.
           */
          getFPS: function getFPS() {
            return model.getFPS();
          },

          /**
           * Returns "simulation progress rate".
           * It indicates how much of simulation time is calculated for
           * one second of real time.
           * @return {number} simulation progress rate.
           */
          getSimulationProgressRate: function getSimulationProgressRate() {
            return model.getSimulationProgressRate();
          },

          startPerformanceTuning: function startPerformanceTuning() {
            model.performanceOptimizer.enable();
          },

          repaint: function repaint() {
            interactivesController.repaintModelView();
          },

          canExportData: function canExportData() {
            var exportController = interactivesController.exportController;
            return exportController && exportController.canExportData() || false;
          },

          isUnexportedDataPresent: function isUnexportedDataPresent() {
            var exportController = interactivesController.exportController;
            return exportController && exportController.isUnexportedDataPresent() || false;
          },

          dataAreAvailableForExport: function dataAreAvailableForExport() {
            var exportController = interactivesController.exportController;
            return exportController && exportController.dataAreAvailableForExport() || false;
          },

          exportData: function exportData() {
            var exportController = interactivesController.exportController;
            if (!exportController || !exportController.canExportData()) {
              throw new Error("No exports have been specified.");
            }
            exportController.exportData();
          },

          getCardinalDirection: function getCardinalDirection(angle, inverse) {
            var direction = ["", ""];
            if (angle > Math.PI/8 && angle < Math.PI*7/8)
              direction[0] = inverse ? "S" : "N";
            else if (angle > Math.PI*9/8 && angle < Math.PI*15/8)
              direction[0] = inverse ? "N" : "S";
            if (angle < Math.PI*3/8 || angle > Math.PI*13/8) {
              direction[1] = inverse ? "W" : "E";
            } else if (angle > Math.PI*5/8 && angle < Math.PI*11/8) {
              direction[1] = inverse ? "E" : "W";
            }
            return direction.join("");
          },

          getCompassDirection: function getCompassDirection(angle, inverse) {
            angle = (angle + Math.PI*3/2) % (2*Math.PI);
            var deg = 360 - angle * 180 / Math.PI;
            return inverse ? (180 + deg) % 360 : deg;
          },

          Math: Math,
          Infinity: Infinity,
          isFinite: isFinite,
          NaN: NaN,
          isNaN: isNaN,

          // Prevent sandbox from overwriting window.undefined (this can still happen in browsers
          // that haven't implemented immutable undefined--mainly IE9, Safari 5)
          undefined: void 0,

          // Rudimentary debugging functionality. Use Lab alert helper function.
          alert: alert,

          // safe versions of setTimeout and setInterval
          setTimeout: function setTimeout(handler) {

            // Ensure that we don't leak "window" to handler function.
            if ( ! handler || handler.constructor !== Function ) {
              throw new TypeError("Must pass a Function instance to Lab's setTimeout.");
            }

            var args = Array.prototype.slice.apply(arguments);
            // By the spec, setTimeout explicitly sets the thisValue of the handler to global object
            // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#timers
            // (work through the "timer initialization steps" algorithm)
            // Ensure that the thisValue is undefined/null:
            args[0] = handler.bind(undefined);
            return window.setTimeout.apply(window, args);
          },

          setInterval: function setInterval(handler) {
            if ( ! handler || handler.constructor !== Function ) {
              throw new TypeError("Must pass a Function instance to Lab's setInterval.");
            }

            var args = Array.prototype.slice.apply(arguments);
            args[0] = handler.bind(undefined);
            return window.setInterval.apply(window, args);
          },

          clearTimeout:  window.clearTimeout,

          clearInterval: window.clearInterval,

          console: window.console !== null ? window.console : {
            log: function() {},
            error: function() {},
            warn: function() {},
            dir: function() {}
          }
        };
      }()),

      /**
       * Current model.
       */
      get model() {
        return model;
      },

      /**
       * InteractivesController instance.
       */
      get intController() {
        return interactivesController;
      },

      /**
       * Bind a new model to Scripting API.
       */
      bindModel: function (newModel) {
        model = newModel;
      },

      /**
        Freeze Scripting API
        Make the scripting API immutable once defined
      */
      freeze: function () {
        Object.freeze(this.api);
      },

      /**
        Extend Scripting API
      */
      extend: function (ModelScriptingAPI) {
        $.extend(this.api, new ModelScriptingAPI(this));
      },

      /**
        Allow console users to try script actions
      */
      exposeScriptingAPI: function () {
        window.script = this.api;
        window.script.run = function(source, args) {
          var prop,
              argNames = [],
              argVals = [];

          for (prop in args) {
            if (args.hasOwnProperty(prop)) {
              argNames.push(prop);
              argVals.push(args[prop]);
            }
          }
          return controller.makeFunctionInScriptContext.apply(null, argNames.concat(source)).apply(null, argVals);
        };
      },

      /**
        Given a script string, return a function that executes that script in a
        context containing *only* the bindings to names we supply.

        This isn't intended for XSS protection (in particular it relies on strict
        mode.) Rather, it's so script authors don't get too clever and start relying
        on accidentally exposed functionality, before we've made decisions about
        what scripting API and semantics we want to support.
      */
      makeFunctionInScriptContext: function () {
            // First n-1 arguments to this function are the names of the arguments to the script.
        var argumentsToScript = Array.prototype.slice.call(arguments, 0, arguments.length - 1),

            // Last argument is the function body of the script, as a string or array of strings.
            scriptSource = arguments[arguments.length - 1],

            scriptFunctionMakerSource,
            scriptFunctionMaker,
            scriptFunction;

        if (typeof scriptSource !== 'string') scriptSource = scriptSource.join('      \n');

        scriptFunctionMakerSource =
          "with (shadowedGlobals) {\n" +
          "  with (scriptingAPI) {\n" +
          "    return function(" + argumentsToScript.join(',') +  ") {\n" +
          "      'use " + "strict';\n" +
          "      " + scriptSource + "\n" +
          "    };\n" +
          "  }\n" +
          "}";

        try {
          scriptFunctionMaker = new Function('shadowedGlobals', 'scriptingAPI', scriptFunctionMakerSource);
          scriptFunction = scriptFunctionMaker(shadowedGlobals, controller.api);
        } catch (e) {
          alert("Error compiling script: \"" + e.toString() + "\"\nScript:\n\n" + scriptSource);
          return function() {
            throw new Error("Cannot run a script that could not be compiled");
          };
        }

        // This function runs the script with all globals shadowed:
        return function() {
          setShadowedGlobals();
          try {
            // invoke the script, passing only enough arguments for the whitelisted names
            return scriptFunction.apply(null, Array.prototype.slice.call(arguments));
          } catch (e) {
            alert("Error running script: \"" + e.toString() + "\"\nScript:\n\n" + scriptSource);
          }
        };
      }
    };

    return controller;
  };
});

/*global define: false */
/**
 * Inherit the prototype methods from one constructor into another.
 *
 * Usage:
 * function ParentClass(a, b) { }
 * ParentClass.prototype.foo = function(a) { }
 *
 * function ChildClass(a, b, c) {
 *   goog.base(this, a, b);
 * }
 *
 * inherit(ChildClass, ParentClass);
 *
 * var child = new ChildClass('a', 'b', 'see');
 * child.foo(); // works
 *
 * In addition, a superclass' implementation of a method can be invoked
 * as follows:
 *
 * ChildClass.prototype.foo = function(a) {
 *   ChildClass.superClass.foo.call(this, a);
 *   // other code
 * };
 *
 * @param {Function} Child Child class.
 * @param {Function} Parent Parent class.
 */
define('common/inherit',[],function() {
  return function inherit(Child, Parent) {
    function F() {}
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.superClass = Parent.prototype;
    Child.prototype.constructor = Child;
  };
});

/*global define */

/**
 * Custom handling of enabled/disabled state for Lab's HTML elements.
 */
define('common/views/view-state',[],function () {

  return {
    disableView: function($element) {
      if (!$element.hasClass("lab-disabled")) {
        $element.addClass("lab-disabled");
        $element.append('<div class="lab-disabled-overlay"/>');
      }
    },

    enableView: function($element) {
      $element.removeClass("lab-disabled");
      $element.find(".lab-disabled-overlay").remove();
    }
  };
});

/*global define */

/**
 * Tiny "mixin" that can be used by an interactive component. It's temporal workaround before we
 * refactor all interactive components to inherit from one common base class that should provide
 * such basic functionality. Mixins are inconvenient in this case, as they force us to modify
 * implementation of every single component (require and use mixin).
 */
define('common/controllers/disablable',['common/views/view-state'],function () {

  var viewState = require('common/views/view-state');
  var enableView = viewState.enableView;
  var disableView = viewState.disableView;

  return function disablable(component, componentDef) {
    // Extend Public API of a component.
    component.setDisabled = function(v) {
      var $element = this.getViewContainer();
      if (v) {
        disableView($element);
        this.isDisabled = true;
      } else {
        enableView($element);
        this.isDisabled = false;
      }
    };

    // Components are effectively enabled until we take specific action to disable them, so:
    component.isDisabled = false;

    // Set initial value if componentDef is provided.
    if (componentDef) {
      component.setDisabled(componentDef.disabled);
    }
  };
});

/*global define, $ */

define('common/controllers/interactive-component',['require','common/controllers/interactive-metadata','common/validator','common/controllers/disablable','common/controllers/help-icon-support'],function (require) {

  var metadata        = require('common/controllers/interactive-metadata'),
      validator       = require('common/validator'),
      disablable      = require('common/controllers/disablable'),
      helpIconSupport = require('common/controllers/help-icon-support');

  /**
   * Basic class for all interactive components.
   *
   * @constructor
   * @param {string} type Component type, should match definition in interactive metadata.
   * @param {Object} component Component JSON definition.
   * @param {ScriptingAPI} scriptingAPI
   * @param {InteractivesController} interactivesController
   */
  function InteractiveComponent(type, component, interactivesController) {
    this._interactivesController = interactivesController;
    this._scriptingAPI = this._interactivesController.getScriptingAPI();
    this._model = this._interactivesController.getModel();

    /**
     * Validated component definition.
     * @type {Object}
     */
    this.component = validator.validateCompleteness(metadata[type], component);
    /**
     * The most outer element. Subclasses should append content to this element.
     * @type {jQuery}
     */
    this.$element = $('<div>').attr("id", component.id).addClass("component");

    // Optionally setup dimensions of the most outer component.
    // Only when metadata and component JSON specifies width and height
    // properties.
    if (this.component.width) {
      this.$element.css("width", this.component.width);
    }
    if (this.component.height) {
      this.$element.css("height", this.component.height);
    }

    if (this.component.disabled) {
      this.setDisabled(this.component.disabled);
    }

    this._optionallyAddOnClickHandlers();

    // optionally add new css classes
    if (this.component.classes && this.component.classes.length) {
      this.$element.addClass(this.component.classes.join(" "));
    }

    // optionally add tooltip
    if (this.component.tooltip) {
      this.$element.attr("title", this.component.tooltip);
    }

    // optionally add help icon
    helpIconSupport(this, this.component, this._interactivesController.helpSystem);
  }

  /**
   * Called when the Interactive Controller reloads the model ... creating a new model and scriptingAPI
   */
  InteractiveComponent.prototype._modelLoadedCallback = function () {
    this._scriptingAPI = this._interactivesController.getScriptingAPI();
    this._model = this._interactivesController.getModel();
    this._optionallyAddOnClickHandlers();
  };

  InteractiveComponent.prototype._updateClickHandler = function (script) {
    // Create a new handler function from action or onClick in string form
    if (typeof script !== "function") {
      this._actionClickFunction = this._scriptingAPI.makeFunctionInScriptContext(script);
    } else {
      this._actionClickFunction = script;
    }
    var that = this;
    this._onClick(this._nameSpace, function() {
      that._actionClickFunction();
    });
    // Also add a special class indicating that this text node is a clickable.
    this.$element.addClass("clickable");
  };

  InteractiveComponent.prototype._optionallyAddOnClickHandlers = function () {
    // Optionally add onClick or action handlers defined with strings in
    // onClick or action property of component.
    if (this.component.onClick !== undefined) {
      this._updateClickHandler(this.component.onClick);
    }
    if (this.component.action !== undefined) {
      this._updateClickHandler(this.component.action);
    }
  };

  InteractiveComponent.prototype.setAction = function (newAction) {
    // If we are passed a string or array of strings as the new action
    // save them in the action property of the component.
    if (typeof script !== "function") {
      this.component.action = newAction;
    }
    this._updateClickHandler(newAction);
  };

  InteractiveComponent.prototype.setOnClick = function (newOnClick) {
    if (this.component.onClick !== undefined) {
      this.component.onClick = newOnClick;
      this._updateClickHandler(this.component.onClick);
    }
  };

  InteractiveComponent.prototype.enableLogging = function (logFunc) {
    var comp = this.component;
    if (comp.onClick === undefined && comp.action === undefined) return; // nothing to log
    var eventName = comp.type[0].toUpperCase() + comp.type.slice(1) + "Clicked";
    var data = {id: comp.id};
    this._onClick(this._nameSpace + "logging", function () {
      data.label = comp.label || comp.text || comp.title;
      logFunc(eventName, data);
    });
  };

  /**
   * @return {jQuery} The most outer element.
   */
  InteractiveComponent.prototype.getViewContainer = function() {
    return this.$element;
  };

  /**
   * @return {Object} Serialized component definition.
   */
  InteractiveComponent.prototype.serialize = function() {
    return this.component;
  };

  InteractiveComponent.prototype._onClick = function(namespace, handler) {
    this.$element.off("click." + namespace);
    this.$element.on("click." + namespace, this._clickTargetSelector || null, handler);
  };

  // It will add .setDisabled() method to the prototype.
  disablable(InteractiveComponent.prototype);

  return InteractiveComponent;
});

/*global define, $ */

define('common/controllers/button-controller',['common/inherit','common/controllers/interactive-component'],function () {
  var inherit              = require('common/inherit'),
      InteractiveComponent = require('common/controllers/interactive-component'),

      buttonControllerCount = 0;

  function ButtonController(component, interactivesController) {
    this._actionClickFunction = function () { };
    this._nameSpace = "button" + (++buttonControllerCount);
    this._clickTargetSelector = 'button';
    // Call super constructor.
    InteractiveComponent.call(this, "button", component, interactivesController);
    this.$element.addClass("interactive-button");
    this.button = $('<button>')
        .html(component.text)
        .appendTo(this.$element);
    this.setAttributes = function(attrs) {
      // only support text changes right now
      if (attrs.text && typeof(attrs.text) !== "undefined") {
        this.component.text = attrs.text;
        this.button.html(attrs.text);
      }
    };
  }

  inherit(ButtonController, InteractiveComponent);

  ButtonController.prototype.modelLoadedCallback = function () {
    ButtonController.superClass._modelLoadedCallback.call(this);
  };

  return ButtonController;
});

/*global define, $ */

define('common/controllers/checkbox-controller',['common/controllers/interactive-metadata','common/controllers/disablable','common/controllers/help-icon-support','common/validator'],function () {

  var metadata        = require('common/controllers/interactive-metadata'),
      disablable      = require('common/controllers/disablable'),
      helpIconSupport = require('common/controllers/help-icon-support'),
      validator       = require('common/validator');

  return function CheckboxController(component, interactivesController) {
    var propertyName,
        actionScript,
        initialValue,
        $checkbox,
        $fakeCheckable,
        $label,
        $element,
        controller,
        model,
        scriptingAPI;

    // Updates checkbox using model property. Used in modelLoadedCallback.
    // Make sure that this function is only called when:
    // a) model is loaded,
    // b) checkbox is bound to some property.
    function updateCheckbox () {
      setCheckbox(model.get(propertyName));
    }

    function updateCheckboxDisabledState() {
      var description = model.getPropertyDescription(propertyName);
      controller.setDisabled(description.getFrozen());
    }

    function setCheckbox(value) {
      if (value) {
        $checkbox.prop('checked', true);
        $fakeCheckable.addClass('checked');
      } else {
        $checkbox.prop('checked', false);
        $fakeCheckable.removeClass('checked');
      }
    }

    function getCheckboxState() {
      return $checkbox.prop('checked');
    }

    function customClickEvent (e) {
      e.preventDefault();

      if ($checkbox.prop('checked')) {
        setCheckbox(false);
      } else {
        setCheckbox(true);
      }
      // Trigger change event!
      $checkbox.trigger('change');
    }

    model = interactivesController.getModel();
    scriptingAPI = interactivesController.getScriptingAPI();

    // Validate component definition, use validated copy of the properties.
    component = validator.validateCompleteness(metadata.checkbox, component);
    propertyName  = component.property;
    actionScript = component.action;
    initialValue  = component.initialValue;

    $label = $('<label>').append('<span>' + component.text + '</span>');
    $label.attr('for', component.id);
    $checkbox = $('<input type="checkbox">').attr('id', component.id);

    if (interactivesController) {
      $checkbox.attr('tabindex', interactivesController.getNextTabIndex());
    }

    $fakeCheckable = $('<div class="fakeCheckable">');
    // Hide native input, use fake checkable.
    $checkbox.css("display", "none");

    // default is to have label on right of checkbox
    if (component.textOn === "left") {
      $element = $('<div>').append($label).append($checkbox).append($fakeCheckable.addClass("right"));
    } else {
      $element = $('<div>').append($checkbox).append($fakeCheckable).append($label);
    }

    // Append class to the most outer container.
    $element.addClass("interactive-checkbox");
    // Each interactive component has to have class "component".
    $element.addClass("component");

    // Ensure that custom div (used for styling) is clickable.
    $fakeCheckable.on('click', customClickEvent);
    // Label also requires custom event handler to ensure that click updates
    // fake clickable element too.
    $label.on('click', customClickEvent);

    // Custom dimensions.
    $element.css({
      width: component.width,
      height: component.height
    });

    // Process onClick script if it is defined.
    if (actionScript) {
      // Create a function which assumes we pass it a parameter called 'value'.
      actionScript = scriptingAPI.makeFunctionInScriptContext('value', actionScript);
    }

    // Register handler for change event.
    $checkbox.on('change', function () {
      var value = false,
          propObj;
      // $(this) will contain a reference to the checkbox.
      if ($(this).is(':checked')) {
        value = true;
      }
      // Change property value if checkbox is connected
      // with model's property.
      if (propertyName !== undefined) {
        propObj = {};
        propObj[propertyName] = value;
        model.set(propObj);
      }
      // Finally, if checkbox has onClick script attached,
      // call it in script context with checkbox status passed.
      if (actionScript !== undefined) {
        actionScript(value);
      }
    });

    if (component.tooltip) {
      $element.attr("title", component.tooltip);
    }

    // Set initial value if provided.
    if (initialValue !== undefined) {
      setCheckbox(initialValue);
    }

    // Public API
    controller = {
      // This callback should be trigger when model is loaded.
      modelLoadedCallback: function () {
        if (model && propertyName !== undefined) {
          model.removeObserver(propertyName, updateCheckbox);
          model.removePropertyDescriptionObserver(propertyName, updateCheckboxDisabledState);
        }
        model = interactivesController.getModel();
        scriptingAPI = interactivesController.getScriptingAPI();

        // Connect checkbox with model's property if its name is defined.
        if (propertyName !== undefined) {
          // Register listener for 'propertyName'.
          model.addPropertiesListener([propertyName], updateCheckbox);
          model.addPropertyDescriptionObserver(propertyName, updateCheckboxDisabledState);
          // Perform initial checkbox setup.
          updateCheckbox();
        }
      },

      enableLogging: function (logFunc) {
        $checkbox.off('change.logging');
        $checkbox.on('change.logging', function () {
          var data = {id: component.id, label: component.text, value: $(this).is(':checked')};
          if (propertyName) data.property = propertyName;
          logFunc('CheckboxChanged', data);
        });
      },

      // Returns view container. Label tag, as it contains checkbox anyway.
      getViewContainer: function () {
        return $element;
      },

      // Returns serialized component definition.
      serialize: function () {
        var result = $.extend(true, {}, component);

        if (propertyName === undefined) {
          // No property binding. Just action script.
          // Update "initialValue" to represent current
          // value of the slider.
          result.initialValue = $checkbox.is(':checked') ? true : false;
        }

        return result;
      }
    };

    disablable(controller, component);
    helpIconSupport(controller, component, interactivesController.helpSystem);

    // Return Public API object.
    return controller;
  };
});

// Released under MIT license
// Copyright (c) 2009-2010 Dominic Baggott
// Copyright (c) 2009-2010 Ash Berlin
// Copyright (c) 2011 Christoph Dorn <christoph@christophdorn.com> (http://www.christophdorn.com)

/*jshint browser:true, devel:true */

(function( expose ) {

/**
 *  class Markdown
 *
 *  Markdown processing in Javascript done right. We have very particular views
 *  on what constitutes 'right' which include:
 *
 *  - produces well-formed HTML (this means that em and strong nesting is
 *    important)
 *
 *  - has an intermediate representation to allow processing of parsed data (We
 *    in fact have two, both as [JsonML]: a markdown tree and an HTML tree).
 *
 *  - is easily extensible to add new dialects without having to rewrite the
 *    entire parsing mechanics
 *
 *  - has a good test suite
 *
 *  This implementation fulfills all of these (except that the test suite could
 *  do with expanding to automatically run all the fixtures from other Markdown
 *  implementations.)
 *
 *  ##### Intermediate Representation
 *
 *  *TODO* Talk about this :) Its JsonML, but document the node names we use.
 *
 *  [JsonML]: http://jsonml.org/ "JSON Markup Language"
 **/
var Markdown = expose.Markdown = function(dialect) {
  switch (typeof dialect) {
    case "undefined":
      this.dialect = Markdown.dialects.Gruber;
      break;
    case "object":
      this.dialect = dialect;
      break;
    default:
      if ( dialect in Markdown.dialects ) {
        this.dialect = Markdown.dialects[dialect];
      }
      else {
        throw new Error("Unknown Markdown dialect '" + String(dialect) + "'");
      }
      break;
  }
  this.em_state = [];
  this.strong_state = [];
  this.debug_indent = "";
};

/**
 *  parse( markdown, [dialect] ) -> JsonML
 *  - markdown (String): markdown string to parse
 *  - dialect (String | Dialect): the dialect to use, defaults to gruber
 *
 *  Parse `markdown` and return a markdown document as a Markdown.JsonML tree.
 **/
expose.parse = function( source, dialect ) {
  // dialect will default if undefined
  var md = new Markdown( dialect );
  return md.toTree( source );
};

/**
 *  toHTML( markdown, [dialect]  ) -> String
 *  toHTML( md_tree ) -> String
 *  - markdown (String): markdown string to parse
 *  - md_tree (Markdown.JsonML): parsed markdown tree
 *
 *  Take markdown (either as a string or as a JsonML tree) and run it through
 *  [[toHTMLTree]] then turn it into a well-formated HTML fragment.
 **/
expose.toHTML = function toHTML( source , dialect , options ) {
  var input = expose.toHTMLTree( source , dialect , options );

  return expose.renderJsonML( input );
};

/**
 *  toHTMLTree( markdown, [dialect] ) -> JsonML
 *  toHTMLTree( md_tree ) -> JsonML
 *  - markdown (String): markdown string to parse
 *  - dialect (String | Dialect): the dialect to use, defaults to gruber
 *  - md_tree (Markdown.JsonML): parsed markdown tree
 *
 *  Turn markdown into HTML, represented as a JsonML tree. If a string is given
 *  to this function, it is first parsed into a markdown tree by calling
 *  [[parse]].
 **/
expose.toHTMLTree = function toHTMLTree( input, dialect , options ) {
  // convert string input to an MD tree
  if ( typeof input ==="string" ) input = this.parse( input, dialect );

  // Now convert the MD tree to an HTML tree

  // remove references from the tree
  var attrs = extract_attr( input ),
      refs = {};

  if ( attrs && attrs.references ) {
    refs = attrs.references;
  }

  var html = convert_tree_to_html( input, refs , options );
  merge_text_nodes( html );
  return html;
};

// For Spidermonkey based engines
function mk_block_toSource() {
  return "Markdown.mk_block( " +
          uneval(this.toString()) +
          ", " +
          uneval(this.trailing) +
          ", " +
          uneval(this.lineNumber) +
          " )";
}

// node
function mk_block_inspect() {
  var util = require("util");
  return "Markdown.mk_block( " +
          util.inspect(this.toString()) +
          ", " +
          util.inspect(this.trailing) +
          ", " +
          util.inspect(this.lineNumber) +
          " )";

}

var mk_block = Markdown.mk_block = function(block, trail, line) {
  // Be helpful for default case in tests.
  if ( arguments.length == 1 ) trail = "\n\n";

  var s = new String(block);
  s.trailing = trail;
  // To make it clear its not just a string
  s.inspect = mk_block_inspect;
  s.toSource = mk_block_toSource;

  if ( line != undefined )
    s.lineNumber = line;

  return s;
};

function count_lines( str ) {
  var n = 0, i = -1;
  while ( ( i = str.indexOf("\n", i + 1) ) !== -1 ) n++;
  return n;
}

// Internal - split source into rough blocks
Markdown.prototype.split_blocks = function splitBlocks( input, startLine ) {
  input = input.replace(/(\r\n|\n|\r)/g, "\n");
  // [\s\S] matches _anything_ (newline or space)
  // [^] is equivalent but doesn't work in IEs.
  var re = /([\s\S]+?)($|\n#|\n(?:\s*\n|$)+)/g,
      blocks = [],
      m;

  var line_no = 1;

  if ( ( m = /^(\s*\n)/.exec(input) ) != null ) {
    // skip (but count) leading blank lines
    line_no += count_lines( m[0] );
    re.lastIndex = m[0].length;
  }

  while ( ( m = re.exec(input) ) !== null ) {
    if (m[2] == "\n#") {
      m[2] = "\n";
      re.lastIndex--;
    }
    blocks.push( mk_block( m[1], m[2], line_no ) );
    line_no += count_lines( m[0] );
  }

  return blocks;
};

/**
 *  Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ]
 *  - block (String): the block to process
 *  - next (Array): the following blocks
 *
 * Process `block` and return an array of JsonML nodes representing `block`.
 *
 * It does this by asking each block level function in the dialect to process
 * the block until one can. Succesful handling is indicated by returning an
 * array (with zero or more JsonML nodes), failure by a false value.
 *
 * Blocks handlers are responsible for calling [[Markdown#processInline]]
 * themselves as appropriate.
 *
 * If the blocks were split incorrectly or adjacent blocks need collapsing you
 * can adjust `next` in place using shift/splice etc.
 *
 * If any of this default behaviour is not right for the dialect, you can
 * define a `__call__` method on the dialect that will get invoked to handle
 * the block processing.
 */
Markdown.prototype.processBlock = function processBlock( block, next ) {
  var cbs = this.dialect.block,
      ord = cbs.__order__;

  if ( "__call__" in cbs ) {
    return cbs.__call__.call(this, block, next);
  }

  for ( var i = 0; i < ord.length; i++ ) {
    //D:this.debug( "Testing", ord[i] );
    var res = cbs[ ord[i] ].call( this, block, next );
    if ( res ) {
      //D:this.debug("  matched");
      if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) )
        this.debug(ord[i], "didn't return a proper array");
      //D:this.debug( "" );
      return res;
    }
  }

  // Uhoh! no match! Should we throw an error?
  return [];
};

Markdown.prototype.processInline = function processInline( block ) {
  return this.dialect.inline.__call__.call( this, String( block ) );
};

/**
 *  Markdown#toTree( source ) -> JsonML
 *  - source (String): markdown source to parse
 *
 *  Parse `source` into a JsonML tree representing the markdown document.
 **/
// custom_tree means set this.tree to `custom_tree` and restore old value on return
Markdown.prototype.toTree = function toTree( source, custom_root ) {
  var blocks = source instanceof Array ? source : this.split_blocks( source );

  // Make tree a member variable so its easier to mess with in extensions
  var old_tree = this.tree;
  try {
    this.tree = custom_root || this.tree || [ "markdown" ];

    blocks:
    while ( blocks.length ) {
      var b = this.processBlock( blocks.shift(), blocks );

      // Reference blocks and the like won't return any content
      if ( !b.length ) continue blocks;

      this.tree.push.apply( this.tree, b );
    }
    return this.tree;
  }
  finally {
    if ( custom_root ) {
      this.tree = old_tree;
    }
  }
};

// Noop by default
Markdown.prototype.debug = function () {
  var args = Array.prototype.slice.call( arguments);
  args.unshift(this.debug_indent);
  if ( typeof print !== "undefined" )
      print.apply( print, args );
  if ( typeof console !== "undefined" && typeof console.log !== "undefined" )
      console.log.apply( null, args );
}

Markdown.prototype.loop_re_over_block = function( re, block, cb ) {
  // Dont use /g regexps with this
  var m,
      b = block.valueOf();

  while ( b.length && (m = re.exec(b) ) != null ) {
    b = b.substr( m[0].length );
    cb.call(this, m);
  }
  return b;
};

/**
 * Markdown.dialects
 *
 * Namespace of built-in dialects.
 **/
Markdown.dialects = {};

/**
 * Markdown.dialects.Gruber
 *
 * The default dialect that follows the rules set out by John Gruber's
 * markdown.pl as closely as possible. Well actually we follow the behaviour of
 * that script which in some places is not exactly what the syntax web page
 * says.
 **/
Markdown.dialects.Gruber = {
  block: {
    atxHeader: function atxHeader( block, next ) {
      var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ );

      if ( !m ) return undefined;

      var header = [ "header", { level: m[ 1 ].length } ];
      Array.prototype.push.apply(header, this.processInline(m[ 2 ]));

      if ( m[0].length < block.length )
        next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) );

      return [ header ];
    },

    setextHeader: function setextHeader( block, next ) {
      var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ );

      if ( !m ) return undefined;

      var level = ( m[ 2 ] === "=" ) ? 1 : 2;
      var header = [ "header", { level : level }, m[ 1 ] ];

      if ( m[0].length < block.length )
        next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) );

      return [ header ];
    },

    code: function code( block, next ) {
      // |    Foo
      // |bar
      // should be a code block followed by a paragraph. Fun
      //
      // There might also be adjacent code block to merge.

      var ret = [],
          re = /^(?: {0,3}\t| {4})(.*)\n?/,
          lines;

      // 4 spaces + content
      if ( !block.match( re ) ) return undefined;

      block_search:
      do {
        // Now pull out the rest of the lines
        var b = this.loop_re_over_block(
                  re, block.valueOf(), function( m ) { ret.push( m[1] ); } );

        if ( b.length ) {
          // Case alluded to in first comment. push it back on as a new block
          next.unshift( mk_block(b, block.trailing) );
          break block_search;
        }
        else if ( next.length ) {
          // Check the next block - it might be code too
          if ( !next[0].match( re ) ) break block_search;

          // Pull how how many blanks lines follow - minus two to account for .join
          ret.push ( block.trailing.replace(/[^\n]/g, "").substring(2) );

          block = next.shift();
        }
        else {
          break block_search;
        }
      } while ( true );

      return [ [ "code_block", ret.join("\n") ] ];
    },

    horizRule: function horizRule( block, next ) {
      // this needs to find any hr in the block to handle abutting blocks
      var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ );

      if ( !m ) {
        return undefined;
      }

      var jsonml = [ [ "hr" ] ];

      // if there's a leading abutting block, process it
      if ( m[ 1 ] ) {
        jsonml.unshift.apply( jsonml, this.processBlock( m[ 1 ], [] ) );
      }

      // if there's a trailing abutting block, stick it into next
      if ( m[ 3 ] ) {
        next.unshift( mk_block( m[ 3 ] ) );
      }

      return jsonml;
    },

    // There are two types of lists. Tight and loose. Tight lists have no whitespace
    // between the items (and result in text just in the <li>) and loose lists,
    // which have an empty line between list items, resulting in (one or more)
    // paragraphs inside the <li>.
    //
    // There are all sorts weird edge cases about the original markdown.pl's
    // handling of lists:
    //
    // * Nested lists are supposed to be indented by four chars per level. But
    //   if they aren't, you can get a nested list by indenting by less than
    //   four so long as the indent doesn't match an indent of an existing list
    //   item in the 'nest stack'.
    //
    // * The type of the list (bullet or number) is controlled just by the
    //    first item at the indent. Subsequent changes are ignored unless they
    //    are for nested lists
    //
    lists: (function( ) {
      // Use a closure to hide a few variables.
      var any_list = "[*+-]|\\d+\\.",
          bullet_list = /[*+-]/,
          number_list = /\d+\./,
          // Capture leading indent as it matters for determining nested lists.
          is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ),
          indent_re = "(?: {0,3}\\t| {4})";

      // TODO: Cache this regexp for certain depths.
      // Create a regexp suitable for matching an li for a given stack depth
      function regex_for_depth( depth ) {

        return new RegExp(
          // m[1] = indent, m[2] = list_type
          "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" +
          // m[3] = cont
          "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})"
        );
      }
      function expand_tab( input ) {
        return input.replace( / {0,3}\t/g, "    " );
      }

      // Add inline content `inline` to `li`. inline comes from processInline
      // so is an array of content
      function add(li, loose, inline, nl) {
        if ( loose ) {
          li.push( [ "para" ].concat(inline) );
          return;
        }
        // Hmmm, should this be any block level element or just paras?
        var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] == "para"
                   ? li[li.length -1]
                   : li;

        // If there is already some content in this list, add the new line in
        if ( nl && li.length > 1 ) inline.unshift(nl);

        for ( var i = 0; i < inline.length; i++ ) {
          var what = inline[i],
              is_str = typeof what == "string";
          if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] == "string" ) {
            add_to[ add_to.length-1 ] += what;
          }
          else {
            add_to.push( what );
          }
        }
      }

      // contained means have an indent greater than the current one. On
      // *every* line in the block
      function get_contained_blocks( depth, blocks ) {

        var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ),
            replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"),
            ret = [];

        while ( blocks.length > 0 ) {
          if ( re.exec( blocks[0] ) ) {
            var b = blocks.shift(),
                // Now remove that indent
                x = b.replace( replace, "");

            ret.push( mk_block( x, b.trailing, b.lineNumber ) );
          }
          else {
            break;
          }
        }
        return ret;
      }

      // passed to stack.forEach to turn list items up the stack into paras
      function paragraphify(s, i, stack) {
        var list = s.list;
        var last_li = list[list.length-1];

        if ( last_li[1] instanceof Array && last_li[1][0] == "para" ) {
          return;
        }
        if ( i + 1 == stack.length ) {
          // Last stack frame
          // Keep the same array, but replace the contents
          last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) );
        }
        else {
          var sublist = last_li.pop();
          last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ), sublist );
        }
      }

      // The matcher function
      return function( block, next ) {
        var m = block.match( is_list_re );
        if ( !m ) return undefined;

        function make_list( m ) {
          var list = bullet_list.exec( m[2] )
                   ? ["bulletlist"]
                   : ["numberlist"];

          stack.push( { list: list, indent: m[1] } );
          return list;
        }


        var stack = [], // Stack of lists for nesting.
            list = make_list( m ),
            last_li,
            loose = false,
            ret = [ stack[0].list ],
            i;

        // Loop to search over block looking for inner block elements and loose lists
        loose_search:
        while ( true ) {
          // Split into lines preserving new lines at end of line
          var lines = block.split( /(?=\n)/ );

          // We have to grab all lines for a li and call processInline on them
          // once as there are some inline things that can span lines.
          var li_accumulate = "";

          // Loop over the lines in this block looking for tight lists.
          tight_search:
          for ( var line_no = 0; line_no < lines.length; line_no++ ) {
            var nl = "",
                l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; });

            // TODO: really should cache this
            var line_re = regex_for_depth( stack.length );

            m = l.match( line_re );
            //print( "line:", uneval(l), "\nline match:", uneval(m) );

            // We have a list item
            if ( m[1] !== undefined ) {
              // Process the previous list item, if any
              if ( li_accumulate.length ) {
                add( last_li, loose, this.processInline( li_accumulate ), nl );
                // Loose mode will have been dealt with. Reset it
                loose = false;
                li_accumulate = "";
              }

              m[1] = expand_tab( m[1] );
              var wanted_depth = Math.floor(m[1].length/4)+1;
              //print( "want:", wanted_depth, "stack:", stack.length);
              if ( wanted_depth > stack.length ) {
                // Deep enough for a nested list outright
                //print ( "new nested list" );
                list = make_list( m );
                last_li.push( list );
                last_li = list[1] = [ "listitem" ];
              }
              else {
                // We aren't deep enough to be strictly a new level. This is
                // where Md.pl goes nuts. If the indent matches a level in the
                // stack, put it there, else put it one deeper then the
                // wanted_depth deserves.
                var found = false;
                for ( i = 0; i < stack.length; i++ ) {
                  if ( stack[ i ].indent != m[1] ) continue;
                  list = stack[ i ].list;
                  stack.splice( i+1, stack.length - (i+1) );
                  found = true;
                  break;
                }

                if (!found) {
                  //print("not found. l:", uneval(l));
                  wanted_depth++;
                  if ( wanted_depth <= stack.length ) {
                    stack.splice(wanted_depth, stack.length - wanted_depth);
                    //print("Desired depth now", wanted_depth, "stack:", stack.length);
                    list = stack[wanted_depth-1].list;
                    //print("list:", uneval(list) );
                  }
                  else {
                    //print ("made new stack for messy indent");
                    list = make_list(m);
                    last_li.push(list);
                  }
                }

                //print( uneval(list), "last", list === stack[stack.length-1].list );
                last_li = [ "listitem" ];
                list.push(last_li);
              } // end depth of shenegains
              nl = "";
            }

            // Add content
            if ( l.length > m[0].length ) {
              li_accumulate += nl + l.substr( m[0].length );
            }
          } // tight_search

          if ( li_accumulate.length ) {
            add( last_li, loose, this.processInline( li_accumulate ), nl );
            // Loose mode will have been dealt with. Reset it
            loose = false;
            li_accumulate = "";
          }

          // Look at the next block - we might have a loose list. Or an extra
          // paragraph for the current li
          var contained = get_contained_blocks( stack.length, next );

          // Deal with code blocks or properly nested lists
          if ( contained.length > 0 ) {
            // Make sure all listitems up the stack are paragraphs
            forEach( stack, paragraphify, this);

            last_li.push.apply( last_li, this.toTree( contained, [] ) );
          }

          var next_block = next[0] && next[0].valueOf() || "";

          if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) {
            block = next.shift();

            // Check for an HR following a list: features/lists/hr_abutting
            var hr = this.dialect.block.horizRule( block, next );

            if ( hr ) {
              ret.push.apply(ret, hr);
              break;
            }

            // Make sure all listitems up the stack are paragraphs
            forEach( stack, paragraphify, this);

            loose = true;
            continue loose_search;
          }
          break;
        } // loose_search

        return ret;
      };
    })(),

    blockquote: function blockquote( block, next ) {
      if ( !block.match( /^>/m ) )
        return undefined;

      var jsonml = [];

      // separate out the leading abutting block, if any. I.e. in this case:
      //
      //  a
      //  > b
      //
      if ( block[ 0 ] != ">" ) {
        var lines = block.split( /\n/ ),
            prev = [],
            line_no = block.lineNumber;

        // keep shifting lines until you find a crotchet
        while ( lines.length && lines[ 0 ][ 0 ] != ">" ) {
            prev.push( lines.shift() );
            line_no++;
        }

        var abutting = mk_block( prev.join( "\n" ), "\n", block.lineNumber );
        jsonml.push.apply( jsonml, this.processBlock( abutting, [] ) );
        // reassemble new block of just block quotes!
        block = mk_block( lines.join( "\n" ), block.trailing, line_no );
      }


      // if the next block is also a blockquote merge it in
      while ( next.length && next[ 0 ][ 0 ] == ">" ) {
        var b = next.shift();
        block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber );
      }

      // Strip off the leading "> " and re-process as a block.
      var input = block.replace( /^> ?/gm, "" ),
          old_tree = this.tree,
          processedBlock = this.toTree( input, [ "blockquote" ] ),
          attr = extract_attr( processedBlock );

      // If any link references were found get rid of them
      if ( attr && attr.references ) {
        delete attr.references;
        // And then remove the attribute object if it's empty
        if ( isEmpty( attr ) ) {
          processedBlock.splice( 1, 1 );
        }
      }

      jsonml.push( processedBlock );
      return jsonml;
    },

    referenceDefn: function referenceDefn( block, next) {
      var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/;
      // interesting matches are [ , ref_id, url, , title, title ]

      if ( !block.match(re) )
        return undefined;

      // make an attribute node if it doesn't exist
      if ( !extract_attr( this.tree ) ) {
        this.tree.splice( 1, 0, {} );
      }

      var attrs = extract_attr( this.tree );

      // make a references hash if it doesn't exist
      if ( attrs.references === undefined ) {
        attrs.references = {};
      }

      var b = this.loop_re_over_block(re, block, function( m ) {

        if ( m[2] && m[2][0] == "<" && m[2][m[2].length-1] == ">" )
          m[2] = m[2].substring( 1, m[2].length - 1 );

        var ref = attrs.references[ m[1].toLowerCase() ] = {
          href: m[2]
        };

        if ( m[4] !== undefined )
          ref.title = m[4];
        else if ( m[5] !== undefined )
          ref.title = m[5];

      } );

      if ( b.length )
        next.unshift( mk_block( b, block.trailing ) );

      return [];
    },

    para: function para( block, next ) {
      // everything's a para!
      return [ ["para"].concat( this.processInline( block ) ) ];
    }
  }
};

Markdown.dialects.Gruber.inline = {

    __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) {
      var m,
          res,
          lastIndex = 0;

      patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__;
      var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" );

      m = re.exec( text );
      if (!m) {
        // Just boring text
        return [ text.length, text ];
      }
      else if ( m[1] ) {
        // Some un-interesting text matched. Return that first
        return [ m[1].length, m[1] ];
      }

      var res;
      if ( m[2] in this.dialect.inline ) {
        res = this.dialect.inline[ m[2] ].call(
                  this,
                  text.substr( m.index ), m, previous_nodes || [] );
      }
      // Default for now to make dev easier. just slurp special and output it.
      res = res || [ m[2].length, m[2] ];
      return res;
    },

    __call__: function inline( text, patterns ) {

      var out = [],
          res;

      function add(x) {
        //D:self.debug("  adding output", uneval(x));
        if ( typeof x == "string" && typeof out[out.length-1] == "string" )
          out[ out.length-1 ] += x;
        else
          out.push(x);
      }

      while ( text.length > 0 ) {
        res = this.dialect.inline.__oneElement__.call(this, text, patterns, out );
        text = text.substr( res.shift() );
        forEach(res, add )
      }

      return out;
    },

    // These characters are intersting elsewhere, so have rules for them so that
    // chunks of plain text blocks don't include them
    "]": function () {},
    "}": function () {},

    __escape__ : /^\\[\\`\*_{}\[\]()#\+.!\-]/,

    "\\": function escaped( text ) {
      // [ length of input processed, node/children to add... ]
      // Only esacape: \ ` * _ { } [ ] ( ) # * + - . !
      if ( this.dialect.inline.__escape__.exec( text ) )
        return [ 2, text.charAt( 1 ) ];
      else
        // Not an esacpe
        return [ 1, "\\" ];
    },

    "![": function image( text ) {

      // Unlike images, alt text is plain text only. no other elements are
      // allowed in there

      // ![Alt text](/path/to/img.jpg "Optional title")
      //      1          2            3       4         <--- captures
      var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ );

      if ( m ) {
        if ( m[2] && m[2][0] == "<" && m[2][m[2].length-1] == ">" )
          m[2] = m[2].substring( 1, m[2].length - 1 );

        m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0];

        var attrs = { alt: m[1], href: m[2] || "" };
        if ( m[4] !== undefined)
          attrs.title = m[4];

        return [ m[0].length, [ "img", attrs ] ];
      }

      // ![Alt text][id]
      m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ );

      if ( m ) {
        // We can't check if the reference is known here as it likely wont be
        // found till after. Check it in md tree->hmtl tree conversion
        return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ];
      }

      // Just consume the '!['
      return [ 2, "![" ];
    },

    "[": function link( text ) {

      var orig = String(text);
      // Inline content is possible inside `link text`
      var res = Markdown.DialectHelpers.inline_until_char.call( this, text.substr(1), "]" );

      // No closing ']' found. Just consume the [
      if ( !res ) return [ 1, "[" ];

      var consumed = 1 + res[ 0 ],
          children = res[ 1 ],
          link,
          attrs;

      // At this point the first [...] has been parsed. See what follows to find
      // out which kind of link we are (reference or direct url)
      text = text.substr( consumed );

      // [link text](/path/to/img.jpg "Optional title")
      //                 1            2       3         <--- captures
      // This will capture up to the last paren in the block. We then pull
      // back based on if there a matching ones in the url
      //    ([here](/url/(test))
      // The parens have to be balanced
      var m = text.match( /^\s*\([ \t]*([^"']*)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ );
      if ( m ) {
        var url = m[1];
        consumed += m[0].length;

        if ( url && url[0] == "<" && url[url.length-1] == ">" )
          url = url.substring( 1, url.length - 1 );

        // If there is a title we don't have to worry about parens in the url
        if ( !m[3] ) {
          var open_parens = 1; // One open that isn't in the capture
          for ( var len = 0; len < url.length; len++ ) {
            switch ( url[len] ) {
            case "(":
              open_parens++;
              break;
            case ")":
              if ( --open_parens == 0) {
                consumed -= url.length - len;
                url = url.substring(0, len);
              }
              break;
            }
          }
        }

        // Process escapes only
        url = this.dialect.inline.__call__.call( this, url, /\\/ )[0];

        attrs = { href: url || "" };
        if ( m[3] !== undefined)
          attrs.title = m[3];

        link = [ "link", attrs ].concat( children );
        return [ consumed, link ];
      }

      // [Alt text][id]
      // [Alt text] [id]
      m = text.match( /^\s*\[(.*?)\]/ );

      if ( m ) {

        consumed += m[ 0 ].length;

        // [links][] uses links as its reference
        attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(),  original: orig.substr( 0, consumed ) };

        link = [ "link_ref", attrs ].concat( children );

        // We can't check if the reference is known here as it likely wont be
        // found till after. Check it in md tree->hmtl tree conversion.
        // Store the original so that conversion can revert if the ref isn't found.
        return [ consumed, link ];
      }

      // [id]
      // Only if id is plain (no formatting.)
      if ( children.length == 1 && typeof children[0] == "string" ) {

        attrs = { ref: children[0].toLowerCase(),  original: orig.substr( 0, consumed ) };
        link = [ "link_ref", attrs, children[0] ];
        return [ consumed, link ];
      }

      // Just consume the "["
      return [ 1, "[" ];
    },


    "<": function autoLink( text ) {
      var m;

      if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) != null ) {
        if ( m[3] ) {
          return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ];

        }
        else if ( m[2] == "mailto" ) {
          return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ];
        }
        else
          return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ];
      }

      return [ 1, "<" ];
    },

    "`": function inlineCode( text ) {
      // Inline code block. as many backticks as you like to start it
      // Always skip over the opening ticks.
      var m = text.match( /(`+)(([\s\S]*?)\1)/ );

      if ( m && m[2] )
        return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ];
      else {
        // TODO: No matching end code found - warn!
        return [ 1, "`" ];
      }
    },

    "  \n": function lineBreak( text ) {
      return [ 3, [ "linebreak" ] ];
    }

};

// Meta Helper/generator method for em and strong handling
function strong_em( tag, md ) {

  var state_slot = tag + "_state",
      other_slot = tag == "strong" ? "em_state" : "strong_state";

  function CloseTag(len) {
    this.len_after = len;
    this.name = "close_" + md;
  }

  return function ( text, orig_match ) {

    if ( this[state_slot][0] == md ) {
      // Most recent em is of this type
      //D:this.debug("closing", md);
      this[state_slot].shift();

      // "Consume" everything to go back to the recrusion in the else-block below
      return[ text.length, new CloseTag(text.length-md.length) ];
    }
    else {
      // Store a clone of the em/strong states
      var other = this[other_slot].slice(),
          state = this[state_slot].slice();

      this[state_slot].unshift(md);

      //D:this.debug_indent += "  ";

      // Recurse
      var res = this.processInline( text.substr( md.length ) );
      //D:this.debug_indent = this.debug_indent.substr(2);

      var last = res[res.length - 1];

      //D:this.debug("processInline from", tag + ": ", uneval( res ) );

      var check = this[state_slot].shift();
      if ( last instanceof CloseTag ) {
        res.pop();
        // We matched! Huzzah.
        var consumed = text.length - last.len_after;
        return [ consumed, [ tag ].concat(res) ];
      }
      else {
        // Restore the state of the other kind. We might have mistakenly closed it.
        this[other_slot] = other;
        this[state_slot] = state;

        // We can't reuse the processed result as it could have wrong parsing contexts in it.
        return [ md.length, md ];
      }
    }
  }; // End returned function
}

Markdown.dialects.Gruber.inline["**"] = strong_em("strong", "**");
Markdown.dialects.Gruber.inline["__"] = strong_em("strong", "__");
Markdown.dialects.Gruber.inline["*"]  = strong_em("em", "*");
Markdown.dialects.Gruber.inline["_"]  = strong_em("em", "_");


// Build default order from insertion order.
Markdown.buildBlockOrder = function(d) {
  var ord = [];
  for ( var i in d ) {
    if ( i == "__order__" || i == "__call__" ) continue;
    ord.push( i );
  }
  d.__order__ = ord;
};

// Build patterns for inline matcher
Markdown.buildInlinePatterns = function(d) {
  var patterns = [];

  for ( var i in d ) {
    // __foo__ is reserved and not a pattern
    if ( i.match( /^__.*__$/) ) continue;
    var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" )
             .replace( /\n/, "\\n" );
    patterns.push( i.length == 1 ? l : "(?:" + l + ")" );
  }

  patterns = patterns.join("|");
  d.__patterns__ = patterns;
  //print("patterns:", uneval( patterns ) );

  var fn = d.__call__;
  d.__call__ = function(text, pattern) {
    if ( pattern != undefined ) {
      return fn.call(this, text, pattern);
    }
    else
    {
      return fn.call(this, text, patterns);
    }
  };
};

Markdown.DialectHelpers = {};
Markdown.DialectHelpers.inline_until_char = function( text, want ) {
  var consumed = 0,
      nodes = [];

  while ( true ) {
    if ( text.charAt( consumed ) == want ) {
      // Found the character we were looking for
      consumed++;
      return [ consumed, nodes ];
    }

    if ( consumed >= text.length ) {
      // No closing char found. Abort.
      return null;
    }

    var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) );
    consumed += res[ 0 ];
    // Add any returned nodes.
    nodes.push.apply( nodes, res.slice( 1 ) );
  }
}

// Helper function to make sub-classing a dialect easier
Markdown.subclassDialect = function( d ) {
  function Block() {}
  Block.prototype = d.block;
  function Inline() {}
  Inline.prototype = d.inline;

  return { block: new Block(), inline: new Inline() };
};

Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block );
Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline );

Markdown.dialects.Maruku = Markdown.subclassDialect( Markdown.dialects.Gruber );

Markdown.dialects.Maruku.processMetaHash = function processMetaHash( meta_string ) {
  var meta = split_meta_hash( meta_string ),
      attr = {};

  for ( var i = 0; i < meta.length; ++i ) {
    // id: #foo
    if ( /^#/.test( meta[ i ] ) ) {
      attr.id = meta[ i ].substring( 1 );
    }
    // class: .foo
    else if ( /^\./.test( meta[ i ] ) ) {
      // if class already exists, append the new one
      if ( attr["class"] ) {
        attr["class"] = attr["class"] + meta[ i ].replace( /./, " " );
      }
      else {
        attr["class"] = meta[ i ].substring( 1 );
      }
    }
    // attribute: foo=bar
    else if ( /\=/.test( meta[ i ] ) ) {
      var s = meta[ i ].split( /\=/ );
      attr[ s[ 0 ] ] = s[ 1 ];
    }
  }

  return attr;
}

function split_meta_hash( meta_string ) {
  var meta = meta_string.split( "" ),
      parts = [ "" ],
      in_quotes = false;

  while ( meta.length ) {
    var letter = meta.shift();
    switch ( letter ) {
      case " " :
        // if we're in a quoted section, keep it
        if ( in_quotes ) {
          parts[ parts.length - 1 ] += letter;
        }
        // otherwise make a new part
        else {
          parts.push( "" );
        }
        break;
      case "'" :
      case '"' :
        // reverse the quotes and move straight on
        in_quotes = !in_quotes;
        break;
      case "\\" :
        // shift off the next letter to be used straight away.
        // it was escaped so we'll keep it whatever it is
        letter = meta.shift();
      default :
        parts[ parts.length - 1 ] += letter;
        break;
    }
  }

  return parts;
}

Markdown.dialects.Maruku.block.document_meta = function document_meta( block, next ) {
  // we're only interested in the first block
  if ( block.lineNumber > 1 ) return undefined;

  // document_meta blocks consist of one or more lines of `Key: Value\n`
  if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) return undefined;

  // make an attribute node if it doesn't exist
  if ( !extract_attr( this.tree ) ) {
    this.tree.splice( 1, 0, {} );
  }

  var pairs = block.split( /\n/ );
  for ( p in pairs ) {
    var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ),
        key = m[ 1 ].toLowerCase(),
        value = m[ 2 ];

    this.tree[ 1 ][ key ] = value;
  }

  // document_meta produces no content!
  return [];
};

Markdown.dialects.Maruku.block.block_meta = function block_meta( block, next ) {
  // check if the last line of the block is an meta hash
  var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ );
  if ( !m ) return undefined;

  // process the meta hash
  var attr = this.dialect.processMetaHash( m[ 2 ] );

  var hash;

  // if we matched ^ then we need to apply meta to the previous block
  if ( m[ 1 ] === "" ) {
    var node = this.tree[ this.tree.length - 1 ];
    hash = extract_attr( node );

    // if the node is a string (rather than JsonML), bail
    if ( typeof node === "string" ) return undefined;

    // create the attribute hash if it doesn't exist
    if ( !hash ) {
      hash = {};
      node.splice( 1, 0, hash );
    }

    // add the attributes in
    for ( a in attr ) {
      hash[ a ] = attr[ a ];
    }

    // return nothing so the meta hash is removed
    return [];
  }

  // pull the meta hash off the block and process what's left
  var b = block.replace( /\n.*$/, "" ),
      result = this.processBlock( b, [] );

  // get or make the attributes hash
  hash = extract_attr( result[ 0 ] );
  if ( !hash ) {
    hash = {};
    result[ 0 ].splice( 1, 0, hash );
  }

  // attach the attributes to the block
  for ( a in attr ) {
    hash[ a ] = attr[ a ];
  }

  return result;
};

Markdown.dialects.Maruku.block.definition_list = function definition_list( block, next ) {
  // one or more terms followed by one or more definitions, in a single block
  var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/,
      list = [ "dl" ],
      i, m;

  // see if we're dealing with a tight or loose block
  if ( ( m = block.match( tight ) ) ) {
    // pull subsequent tight DL blocks out of `next`
    var blocks = [ block ];
    while ( next.length && tight.exec( next[ 0 ] ) ) {
      blocks.push( next.shift() );
    }

    for ( var b = 0; b < blocks.length; ++b ) {
      var m = blocks[ b ].match( tight ),
          terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ),
          defns = m[ 2 ].split( /\n:\s+/ );

      // print( uneval( m ) );

      for ( i = 0; i < terms.length; ++i ) {
        list.push( [ "dt", terms[ i ] ] );
      }

      for ( i = 0; i < defns.length; ++i ) {
        // run inline processing over the definition
        list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) );
      }
    }
  }
  else {
    return undefined;
  }

  return [ list ];
};

// splits on unescaped instances of @ch. If @ch is not a character the result
// can be unpredictable

Markdown.dialects.Maruku.block.table = function table (block, next) {

    var _split_on_unescaped = function(s, ch) {
        ch = ch || '\\s';
        if (ch.match(/^[\\|\[\]{}?*.+^$]$/)) { ch = '\\' + ch; }
        var res = [ ],
            r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'),
            m;
        while(m = s.match(r)) {
            res.push(m[1]);
            s = m[2];
        }
        res.push(s);
        return res;
    }

    var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/,
        // find at least an unescaped pipe in each line
        no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/,
        i, m;
    if (m = block.match(leading_pipe)) {
        // remove leading pipes in contents
        // (header and horizontal rule already have the leading pipe left out)
        m[3] = m[3].replace(/^\s*\|/gm, '');
    } else if (! ( m = block.match(no_leading_pipe))) {
        return undefined;
    }

    var table = [ "table", [ "thead", [ "tr" ] ], [ "tbody" ] ];

    // remove trailing pipes, then split on pipes
    // (no escaped pipes are allowed in horizontal rule)
    m[2] = m[2].replace(/\|\s*$/, '').split('|');

    // process alignment
    var html_attrs = [ ];
    forEach (m[2], function (s) {
        if (s.match(/^\s*-+:\s*$/))       html_attrs.push({align: "right"});
        else if (s.match(/^\s*:-+\s*$/))  html_attrs.push({align: "left"});
        else if (s.match(/^\s*:-+:\s*$/)) html_attrs.push({align: "center"});
        else                              html_attrs.push({});
    });

    // now for the header, avoid escaped pipes
    m[1] = _split_on_unescaped(m[1].replace(/\|\s*$/, ''), '|');
    for (i = 0; i < m[1].length; i++) {
        table[1][1].push(['th', html_attrs[i] || {}].concat(
            this.processInline(m[1][i].trim())));
    }

    // now for body contents
    forEach (m[3].replace(/\|\s*$/mg, '').split('\n'), function (row) {
        var html_row = ['tr'];
        row = _split_on_unescaped(row, '|');
        for (i = 0; i < row.length; i++) {
            html_row.push(['td', html_attrs[i] || {}].concat(this.processInline(row[i].trim())));
        }
        table[2].push(html_row);
    }, this);

    return [table];
}

Markdown.dialects.Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) {
  if ( !out.length ) {
    return [ 2, "{:" ];
  }

  // get the preceeding element
  var before = out[ out.length - 1 ];

  if ( typeof before === "string" ) {
    return [ 2, "{:" ];
  }

  // match a meta hash
  var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ );

  // no match, false alarm
  if ( !m ) {
    return [ 2, "{:" ];
  }

  // attach the attributes to the preceeding element
  var meta = this.dialect.processMetaHash( m[ 1 ] ),
      attr = extract_attr( before );

  if ( !attr ) {
    attr = {};
    before.splice( 1, 0, attr );
  }

  for ( var k in meta ) {
    attr[ k ] = meta[ k ];
  }

  // cut out the string and replace it with nothing
  return [ m[ 0 ].length, "" ];
};

Markdown.dialects.Maruku.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/;

Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block );
Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline );

var isArray = Array.isArray || function(obj) {
  return Object.prototype.toString.call(obj) == "[object Array]";
};

var forEach;
// Don't mess with Array.prototype. Its not friendly
if ( Array.prototype.forEach ) {
  forEach = function( arr, cb, thisp ) {
    return arr.forEach( cb, thisp );
  };
}
else {
  forEach = function(arr, cb, thisp) {
    for (var i = 0; i < arr.length; i++) {
      cb.call(thisp || arr, arr[i], i, arr);
    }
  }
}

var isEmpty = function( obj ) {
  for ( var key in obj ) {
    if ( hasOwnProperty.call( obj, key ) ) {
      return false;
    }
  }

  return true;
}

function extract_attr( jsonml ) {
  return isArray(jsonml)
      && jsonml.length > 1
      && typeof jsonml[ 1 ] === "object"
      && !( isArray(jsonml[ 1 ]) )
      ? jsonml[ 1 ]
      : undefined;
}



/**
 *  renderJsonML( jsonml[, options] ) -> String
 *  - jsonml (Array): JsonML array to render to XML
 *  - options (Object): options
 *
 *  Converts the given JsonML into well-formed XML.
 *
 *  The options currently understood are:
 *
 *  - root (Boolean): wether or not the root node should be included in the
 *    output, or just its children. The default `false` is to not include the
 *    root itself.
 */
expose.renderJsonML = function( jsonml, options ) {
  options = options || {};
  // include the root element in the rendered output?
  options.root = options.root || false;

  var content = [];

  if ( options.root ) {
    content.push( render_tree( jsonml ) );
  }
  else {
    jsonml.shift(); // get rid of the tag
    if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) {
      jsonml.shift(); // get rid of the attributes
    }

    while ( jsonml.length ) {
      content.push( render_tree( jsonml.shift() ) );
    }
  }

  return content.join( "\n\n" );
};

function escapeHTML( text ) {
  return text.replace( /&/g, "&amp;" )
             .replace( /</g, "&lt;" )
             .replace( />/g, "&gt;" )
             .replace( /"/g, "&quot;" )
             .replace( /'/g, "&#39;" );
}

function render_tree( jsonml ) {
  // basic case
  if ( typeof jsonml === "string" ) {
    return escapeHTML( jsonml );
  }

  var tag = jsonml.shift(),
      attributes = {},
      content = [];

  if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) {
    attributes = jsonml.shift();
  }

  while ( jsonml.length ) {
    content.push( render_tree( jsonml.shift() ) );
  }

  var tag_attrs = "";
  for ( var a in attributes ) {
    tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"';
  }

  // be careful about adding whitespace here for inline elements
  if ( tag == "img" || tag == "br" || tag == "hr" ) {
    return "<"+ tag + tag_attrs + "/>";
  }
  else {
    return "<"+ tag + tag_attrs + ">" + content.join( "" ) + "</" + tag + ">";
  }
}

function convert_tree_to_html( tree, references, options ) {
  var i;
  options = options || {};

  // shallow clone
  var jsonml = tree.slice( 0 );

  if ( typeof options.preprocessTreeNode === "function" ) {
      jsonml = options.preprocessTreeNode(jsonml, references);
  }

  // Clone attributes if they exist
  var attrs = extract_attr( jsonml );
  if ( attrs ) {
    jsonml[ 1 ] = {};
    for ( i in attrs ) {
      jsonml[ 1 ][ i ] = attrs[ i ];
    }
    attrs = jsonml[ 1 ];
  }

  // basic case
  if ( typeof jsonml === "string" ) {
    return jsonml;
  }

  // convert this node
  switch ( jsonml[ 0 ] ) {
    case "header":
      jsonml[ 0 ] = "h" + jsonml[ 1 ].level;
      delete jsonml[ 1 ].level;
      break;
    case "bulletlist":
      jsonml[ 0 ] = "ul";
      break;
    case "numberlist":
      jsonml[ 0 ] = "ol";
      break;
    case "listitem":
      jsonml[ 0 ] = "li";
      break;
    case "para":
      jsonml[ 0 ] = "p";
      break;
    case "markdown":
      jsonml[ 0 ] = "html";
      if ( attrs ) delete attrs.references;
      break;
    case "code_block":
      jsonml[ 0 ] = "pre";
      i = attrs ? 2 : 1;
      var code = [ "code" ];
      code.push.apply( code, jsonml.splice( i, jsonml.length - i ) );
      jsonml[ i ] = code;
      break;
    case "inlinecode":
      jsonml[ 0 ] = "code";
      break;
    case "img":
      jsonml[ 1 ].src = jsonml[ 1 ].href;
      delete jsonml[ 1 ].href;
      break;
    case "linebreak":
      jsonml[ 0 ] = "br";
    break;
    case "link":
      jsonml[ 0 ] = "a";
      break;
    case "link_ref":
      jsonml[ 0 ] = "a";

      // grab this ref and clean up the attribute node
      var ref = references[ attrs.ref ];

      // if the reference exists, make the link
      if ( ref ) {
        delete attrs.ref;

        // add in the href and title, if present
        attrs.href = ref.href;
        if ( ref.title ) {
          attrs.title = ref.title;
        }

        // get rid of the unneeded original text
        delete attrs.original;
      }
      // the reference doesn't exist, so revert to plain text
      else {
        return attrs.original;
      }
      break;
    case "img_ref":
      jsonml[ 0 ] = "img";

      // grab this ref and clean up the attribute node
      var ref = references[ attrs.ref ];

      // if the reference exists, make the link
      if ( ref ) {
        delete attrs.ref;

        // add in the href and title, if present
        attrs.src = ref.href;
        if ( ref.title ) {
          attrs.title = ref.title;
        }

        // get rid of the unneeded original text
        delete attrs.original;
      }
      // the reference doesn't exist, so revert to plain text
      else {
        return attrs.original;
      }
      break;
  }

  // convert all the children
  i = 1;

  // deal with the attribute node, if it exists
  if ( attrs ) {
    // if there are keys, skip over it
    for ( var key in jsonml[ 1 ] ) {
        i = 2;
        break;
    }
    // if there aren't, remove it
    if ( i === 1 ) {
      jsonml.splice( i, 1 );
    }
  }

  for ( ; i < jsonml.length; ++i ) {
    jsonml[ i ] = convert_tree_to_html( jsonml[ i ], references, options );
  }

  return jsonml;
}


// merges adjacent text nodes into a single node
function merge_text_nodes( jsonml ) {
  // skip the tag name and attribute hash
  var i = extract_attr( jsonml ) ? 2 : 1;

  while ( i < jsonml.length ) {
    // if it's a string check the next item too
    if ( typeof jsonml[ i ] === "string" ) {
      if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) {
        // merge the second string into the first and remove it
        jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ];
      }
      else {
        ++i;
      }
    }
    // if it's not a string recurse
    else {
      merge_text_nodes( jsonml[ i ] );
      ++i;
    }
  }
}

} )( (function() {
  if ( typeof exports === "undefined" ) {
    window.markdown = {};
    return window.markdown;
  }
  else {
    return exports;
  }
} )() );

define("markdown", (function (global) {
    return function () {
        var ret, fn;
        return ret || global.markdown;
    };
}(this)));

/*global define: true, $ */

/**
 * Converts markdown to HTML. Passed argument can be a string or array of strings.
 */
define('common/markdown-to-html',['require','markdown'],function (require) {
  var markdown = require('markdown'),
      NEW_WINDOW = 'class="opens-in-new-window" target="blank"';

  return function markdownToHTML(text) {
    var content = "", html;
    if (!$.isArray(text)) text = [text];
    text.forEach(function (line) {
      content += line + "\n";
    });
    html = '<div class="markdown-typography">' + markdown.toHTML(content) + '</div>';
    return html.replace(/<a(.*?)>/g, "<a$1 " + NEW_WINDOW + ">");
  };
});

/*global define */

define('common/controllers/text-controller',['require','common/markdown-to-html','common/inherit','common/controllers/interactive-component'],function (require) {

  var markdownToHTML       = require('common/markdown-to-html'),
      inherit              = require('common/inherit'),
      InteractiveComponent = require('common/controllers/interactive-component');

  /**
   * Text controller.
   * It supports markdown (syntax: http://daringfireball.net/projects/markdown/syntax).
   *
   * @constructor
   * @extends InteractiveComponent
   * @param {Object} component Component JSON definition.
   * @param {ScriptingAPI} scriptingAPI
   * @param {InteracitveController} interacitveController
   */
  function TextController(component, interactivesController) {
    // Call super constructor.
    InteractiveComponent.call(this, "text", component, interactivesController);
    // Setup custom class.
    this.$element.addClass("interactive-text");
    // Use markdown to parse the 'text' content.
    this.$element.append(markdownToHTML(this.component.text));
  }
  inherit(TextController, InteractiveComponent);

  TextController.prototype.modelLoadedCallback = function () {
    TextController.superClass._modelLoadedCallback.call(this);
  };

  return TextController;
});

/*global define, $ */

define('common/controllers/image-controller',['require','lab.config','common/inherit','common/controllers/interactive-component'],function (require) {

  var labConfig            = require('lab.config'),
      inherit              = require('common/inherit'),
      InteractiveComponent = require('common/controllers/interactive-component'),

      externalUrl  = /^https?:\/\//i;

  /**
   * Image controller.
   *
   * @constructor
   * @extends InteractiveComponent
   * @param {Object} component Component JSON definition.
   * @param {ScriptingAPI} scriptingAPI
   * @param {InteractiveController} controller
   */
  function ImageController(component, controller) {
    // Call super constructor.
    InteractiveComponent.call(this, "image", component, controller);

    /** @private */
    this._controller = controller;
    /** @private */
    this._$img = $("<img>");
    /** @private */
    this._externalUrl = externalUrl.test(this.component.src);

    if (this._externalUrl) {
      // If URL is external, we can setup it just once.
      this._$img.attr("src", this.component.src);
    }

    // When a dimension is different from "auto",
    // ensure that image fits its parent container.
    if (this.component.width !== "auto") {
      this._$img.css("width", "100%");
    }
    if (this.component.height !== "auto") {
      this._$img.css("height", "100%");
    }
    this._$img.appendTo(this.$element);
  }
  inherit(ImageController, InteractiveComponent);

  /**
   * Implements optional callback supported by Interactive Controller.
   */
  ImageController.prototype.modelLoadedCallback = function() {
    var src, modelUrl, urlRelativeTo;
    // It's necessary to update path only if its relative (as it's relative to
    // model file).
    if (!this._externalUrl) {
      src = this.component.src;
      // Relative path should be relative to the model definition file, to
      // follow pattern used for images inside model container.
      // TODO: not sure if it makes sense for the Interactive images. When web
      // application is ready, probably it will be changed anyway.
      urlRelativeTo = this.component.urlRelativeTo;

      switch(urlRelativeTo) {
        case 'page':
          modelUrl = '';
          break;
        case 'model':
        default:
          modelUrl = this._controller.modelController.modelUrl || '';
          break;
      }

      // Remove <model-name>.json from url.
      modelUrl = modelUrl.slice(0, modelUrl.lastIndexOf("/") + 1);
      src = labConfig.modelsRootUrl + modelUrl + src;
      this._$img.attr("src", src);
    }
  };

  return ImageController;
});

/*global define, $ */
/*jshint loopfunc: true */

define('common/controllers/radio-controller',['common/controllers/interactive-metadata','common/validator','common/controllers/disablable','common/controllers/help-icon-support'],function () {

  var metadata        = require('common/controllers/interactive-metadata'),
      validator       = require('common/validator'),
      disablable      = require('common/controllers/disablable'),
      helpIconSupport = require('common/controllers/help-icon-support');

  return function RadioController(component, interactivesController) {
        // Public API.
    var controller,
        // DOM elements.
        $div,
        // Options definitions from component JSON definition.
        options,
        // List of jQuery objects wrapping <input type="radio"> elements.
        $inputs = [],
        // List of jQuery objects wrapping option <div>.
        $options = [],
        model,
        scriptingAPI;

    // Updates radio using model property. Used in modelLoadedCallback.
    // Make sure that this function is only called when:
    // a) model is loaded,
    // b) radio is bound to some property.
    function updateRadio() {
      if (component.property !== undefined) {
        var value = model.get(component.property);
        for (var i = 0, len = options.length; i < len; i++) {
          if (options[i].value === value) {
            $inputs[i].attr("checked", true);
            $options[i].addClass('checked');
          } else {
            $inputs[i].removeAttr("checked");
            $options[i].removeClass('checked');
          }
        }
      }
    }

    function updateRadioDisabledState() {
      var description = model.getPropertyDescription(component.property);
      controller.setDisabled(description.getFrozen());
    }


    function customClickEvent (e) {
      var $span = $(this),
          $input = $span.find('input'),
          i, len;

      e.preventDefault();

      if ($input.attr("disabled") !== undefined) {
        // Do nothing when option is disabled.
        return;
      }

      for (i = 0, len = $inputs.length; i < len; i++) {
        $inputs[i].removeAttr('checked');
        $options[i].removeClass('checked');
      }

      $input.attr('checked', 'checked');
      $span.addClass('checked');

      // Trigger change event!
      $input.trigger('change');
    }

    function initialize() {
      var $input, $span, $label, $optionsContainer,
          option, i, len;

      model = interactivesController.getModel();
      scriptingAPI = interactivesController.getScriptingAPI();

      // Validate component definition, use validated copy of the properties.
      component = validator.validateCompleteness(metadata.radio, component);
      // Validate radio options too.
      options = component.options;
      for (i = 0, len = options.length; i < len; i++) {
        options[i] = validator.validateCompleteness(metadata.radioOption, options[i]);
      }

      // Create HTML elements.
      $div = $('<div>').attr('id', component.id);
      $div.addClass("interactive-radio");
      // Each interactive component has to have class "component".
      $div.addClass("component");
      // Add class defining component orientation - "horizontal" or "vertical".
      $div.addClass(component.orientation);
      // "radio" or "toggle".
      $div.addClass(component.style);

      if (component.label) {
        $label = $("<span>").html(component.label);
        $label.addClass("label");
        $label.addClass(component.labelOn === "top" ? "on-top" : "on-left");
        $div.append($label);
      }

      $optionsContainer = $("<span>").addClass("options").appendTo($div);

      // Create options (<input type="radio">)
      for (i = 0, len = options.length; i < len; i++) {
        option = options[i];
        $input = $('<input>')
          .attr('type', "radio")
          .attr('name', component.id)
          .attr('tabindex', interactivesController.getNextTabIndex())
          .attr('id', component.id + '-' + i);
        $inputs.push($input);

        $span = $('<span>')
          .addClass('option')
          .append($input);
        $options.push($span);
        $optionsContainer.append($span);

        if (component.style === 'radio') {
          $('<div class="fakeCheckable">').appendTo($span);
        }

        $('<label>')
          .attr("for", component.id + '-' + i)
          .html(option.text)
          .appendTo($span);

        if (option.disabled) {
          $input.attr("disabled", option.disabled);
          $span.addClass('lab-disabled');
        }
        if (option.selected) {
          $input.attr("checked", option.selected);
          $span.addClass("checked");
        }

        $span.on('touchstart click', customClickEvent);

        $input.change((function(option) {
          return function() {
            if (option.action){
              scriptingAPI.makeFunctionInScriptContext(option.action)();
            } else if (option.value !== undefined) {
              model.set(component.property, option.value);
            }
          };
        })(option));
      }

      if (component.tooltip) {
        $div.attr("title", component.tooltip);
      }

      disablable(controller, component);
      helpIconSupport(controller, component, interactivesController.helpSystem);
    }

    // Public API.
    controller = {
      modelLoadedCallback: function () {
        if (model && component.property !== undefined) {
          model.removeObserver(component.property, updateRadio);
          model.removePropertyDescriptionObserver(component.property, updateRadioDisabledState);
        }
        model = interactivesController.getModel();
        scriptingAPI = interactivesController.getScriptingAPI();
        // Connect radio with model's property if its name is defined.
        if (component.property !== undefined) {
          // Register listener for property.
          model.addPropertiesListener([component.property], updateRadio);
          model.addPropertyDescriptionObserver(component.property, updateRadioDisabledState);
        }
        // Perform initial radio setup.
        updateRadio();
      },

      enableLogging: function (logFunc) {
        $inputs.forEach(function ($input, idx) {
          var optionSpec = options[idx];
          $input.off('.logging');
          $input.on('change.logging', function () {
            var data = {id: component.id, selected: optionSpec.text};
            if (component.label) data.label = component.label;
            if (component.property) {
              data.property = component.property;
              data.value = optionSpec.value;
            }
            logFunc('RadioChanged', data);
          });
        });
      },

      // Returns view container.
      getViewContainer: function () {
        return $div;
      },

      // Returns serialized component definition.
      serialize: function () {
        var i, len;
        if (component.property === undefined) {
          // When property binding is not defined, we need to keep track
          // which option is currently selected.
          for (i = 0, len = options.length; i < len; i++) {
            if ($inputs[i].attr("checked")) {
              options[i].selected = true;
            } else {
              delete options[i].selected;
            }
          }
        }
        // Note that 'options' array above is a reference to component.options array.
        // Every thing is updated, return a copy.
        return $.extend(true, {}, component);
      }
    };

    initialize();

    // Return Public API object.
    return controller;
  };
});

/*global define, $*/

define('common/controllers/slider-controller',['common/controllers/interactive-metadata','common/validator','common/controllers/disablable','common/controllers/help-icon-support'],function () {

  var metadata        = require('common/controllers/interactive-metadata'),
      validator       = require('common/validator'),
      disablable      = require('common/controllers/disablable'),
      helpIconSupport = require('common/controllers/help-icon-support');

  return function SliderController(component, interactivesController) {
    var min, max, steps, propertyName,
        actionFunc, initialValue,
        title, labels, displayValue, displayFunc,
        i, label,
        fillColor, fillToValue, fillToPct, defaultBackgroundColor = null,
        // View elements.
        $elem,
        $title,
        $label,
        $slider,
        $sliderHandle,
        $container,
        model,
        scriptingAPI,
        // Public API object.
        controller,

        // Updates slider using model property. Used in modelLoadedCallback.
        // Make sure that this function is only called when:
        // a) model is loaded,
        // b) slider is bound to some property.
        updateSlider = function () {
          var value = interactivesController.getModel().get(propertyName);
          $slider.slider('value', value);
          redoSliderFill(value);
          if (displayValue) {
            $sliderHandle.text(displayFunc(value));
          }
        },
        updateSliderDisabledState = function () {
          var description = model.getPropertyDescription(propertyName);
          controller.setDisabled(description.getFrozen());
        };

    function bindTargets() {
      $slider.off('.componentAction');
      // Bind action or/and property, process other options.
      if (component.action) {
        // The 'action' property is a source of a function which assumes we pass it a parameter
        // called 'value'.
        actionFunc = scriptingAPI.makeFunctionInScriptContext('value', component.action);
        $slider.on('slide.componentAction', function(event, ui) {
          actionFunc(ui.value);
          if (displayValue) {
            $sliderHandle.text(displayFunc(ui.value));
          }
        });
      }

      if (propertyName) {
        $slider.on('slide.componentAction', function(event, ui) {
          // Just ignore slide events that occur before the model is loaded.
          var obj = {};
          obj[propertyName] = ui.value;
          if (model) model.set(obj);
          if (displayValue) {
            $sliderHandle.text(displayFunc(ui.value));
          }
        });
      }

      if (displayValue) {
        displayFunc = scriptingAPI.makeFunctionInScriptContext('value', displayValue);
      }
    }

    function redoSliderFill(value) {
      // linear-gradient isn't supported on IE 9, but IE 9 doesn't seem to support multi-stop gradients anyway.
      // It falls back to the same behavior as not having the fill color defined.
      if (fillColor) {
        var valuePct = Math.round(100 * (value - min) / (max - min)),
            gradientStr = '',
            webkitGradientStr = '',
            stops = [];

        if (defaultBackgroundColor === null) {
          $container.css('background', '');
          defaultBackgroundColor = $container.css('background-color');
        }

        // Figure out our gradient string
        if (value === fillToValue) {
          // remove the gradient entirely when we're on top of the value we're filling to
          $container.css('background', '');
        } else {
          // min stop
          gradientStr += defaultBackgroundColor + ' 0%, ';
          webkitGradientStr += 'color-stop(0%,' + defaultBackgroundColor + '), ';

          // next the value and fillToValue stops
          if (fillToValue <= value) {
            stops.push(fillToPct);
            stops.push(valuePct);
          } else {
            stops.push(valuePct);
            stops.push(fillToPct);
          }

          // we're the default color to the left of the first stop, and the fillColor to the right
          gradientStr += defaultBackgroundColor + ' ' + stops[0] + '%, ';
          webkitGradientStr += 'color-stop(' + stops[0] + '%,' + defaultBackgroundColor + '), ';

          gradientStr += fillColor + ' ' + stops[0] + '%, ';
          webkitGradientStr += 'color-stop(' + stops[0] + '%,' + fillColor + '), ';

          // All the way up to the next stop, then we revert back to the default color
          gradientStr += fillColor + ' ' + stops[1] + '%, ';
          webkitGradientStr += 'color-stop(' + stops[1] + '%,' + fillColor + '), ';

          gradientStr += defaultBackgroundColor + ' ' + stops[1] + '%, ';
          webkitGradientStr += 'color-stop(' + stops[1] + '%,' + defaultBackgroundColor + '), ';

          // And then we're the default color up to the max
          gradientStr += defaultBackgroundColor + ' 100%';
          webkitGradientStr += 'color-stop(100%,' + defaultBackgroundColor + ')';

          $container.css('background', '-webkit-gradient(linear, left top, right top, ' + webkitGradientStr + ')');
          $container.css('background', '-webkit-linear-gradient(left, ' + gradientStr + ')');
          $container.css('background', '-moz-linear-gradient(left, ' + gradientStr + ')');
          $container.css('background', '-o-linear-gradient(left, ' + gradientStr + ')');
          $container.css('background', 'linear-gradient(to right, ' + gradientStr + ')');
        }
      }
    }

    function initialize() {
      //
      // Initialize.
      //
      scriptingAPI = interactivesController.getScriptingAPI();
      model = interactivesController.getModel();
      // Validate component definition, use validated copy of the properties.
      component = validator.validateCompleteness(metadata.slider, component);
      min = component.min;
      max = component.max;
      steps = component.steps;
      propertyName = component.property;
      initialValue = component.initialValue;
      title = component.title;
      labels = component.labels;
      displayValue = component.displayValue;
      fillColor = component.fillColor;
      fillToValue = component.fillToValue;

      model = interactivesController.getModel();

      // Setup view.
      if (min === undefined) min = 0;
      if (max === undefined) max = 10;
      if (steps === undefined) steps = 10;
      if (fillToValue === undefined) fillToValue = min;

      fillToPct = Math.round(100 * (fillToValue - min) / (max - min));

      $title = $('<div class="title ' + component.titlePosition + '">' + title + '</div>');
      // we pick up the SVG slider component CSS if we use the generic class name 'slider'
      $container = $('<div class="container">');
      $slider = $('<div class="html-slider">').attr('id', component.id);
      $slider.appendTo($container);

      $slider.slider({
        min: min,
        max: max,
        step: (max - min) / steps
      });

      if (fillColor) {
        $slider.addClass('has-fill');
        $slider.on('slide', function(evt, ui) {
          redoSliderFill(ui.value);
        });
      }

      $sliderHandle = $slider.find(".ui-slider-handle");

      $sliderHandle.attr('tabindex', interactivesController.getNextTabIndex());

      $elem = $('<div class="interactive-slider">');
      if (component.titlePosition === "right" || component.titlePosition === "bottom") {
        $elem.append($container)
             .append($title);
      } else {
        $elem.append($title)
             .append($container);
      }

      if (component.titlePosition === "left" || component.titlePosition === "right") {
        $container.css({ display: 'inline-block' });
      }

      // Each interactive component has to have class "component".
      $elem.addClass("component");

      // Apply custom width and height settings.
      // Also not that we set dimensions of the most outer container, not slider.
      // Slider itself will always follow dimensions of container DIV.
      // We have to do it that way to ensure that labels refer correct dimensions.
      $elem.css({
        "width": component.width,
        "height": component.height
      });
      if (component.width === "auto") {
        // Ensure that min width is 12em, when width is set to "auto".
        // Prevent from situation when all sliders with short labels have
        // different widths, what looks distracting.
        $elem.css("min-width", "12em");
      }

      var leftLabelWidth = null;
      var rightLabelWidth = null;
      var getLabelWidth = function(labelSelector) {
        // This is quite tricky - we need to calculate label width only when we add the whole
        // slider element to the interactive container. It ensures that styles are applied correctly
        // and font sizes are final.
        return $elem.measure(function() {
          // Use font size of the container, not label itself!
          // Note that this refers to the label (take a look at $.measure function).
          return (this.width() / parseFloat(this.parent().css('font-size')));
        }, labelSelector, interactivesController.interactiveContainer);
      };
      for (i = 0; i < labels.length; i++) {
        label = labels[i];
        $label = $('<p class="label">' + label.label + '</p>');
        $container.append($label);
        if (label.value === 'right') {
          // Special kind of label which is on the right side of the slider.
          $label.addClass('side right');
          rightLabelWidth = getLabelWidth('.label.side.right');
          $label.css({
            'left': '100%',
            'margin-left': '0.6em'
          });
        } else if (label.value === 'left') {
          // Special kind of label which is on the left side of the slider.
          $label.addClass('side left');
          leftLabelWidth = getLabelWidth('.label.side.left');
          $label.css({
            'left': (-leftLabelWidth) + 'em',
            'margin-left': '-0.8em'
          });
        } else {
          $label.css('left', (label.value - min) / (max - min) * 100 + '%');
        }
      }
      // Theoretically we should also include left margins (0.6em + 0.8em),
      // but slider always has small padding to handle regular labels,
      // so it's not necessary in practice.
      if (leftLabelWidth) $elem.css('margin-left', leftLabelWidth + 'em');
      if (rightLabelWidth) $elem.css('margin-right', rightLabelWidth + 'em');

      bindTargets();

      if (component.tooltip) {
        $elem.attr("title", component.tooltip);
      }

      disablable(controller, component);
      helpIconSupport(controller, component, interactivesController.helpSystem);

      // Prevent keyboard control of slider from stepping the model backwards and forwards
      $sliderHandle.on('keydown.slider-handle', function(event) {
          event.stopPropagation();
      });

      // Call resize function to support complex resizing when height is different from "auto".
      controller.resize();

      // Finally set the initial value if it's provided.
      if (initialValue !== undefined && initialValue !== null) {
        $slider.slider('value', initialValue);
        redoSliderFill(initialValue);
        if (displayValue) {
          $sliderHandle.text(displayFunc(initialValue));
        }
      }
    }

    // Public API.
    controller = {
      // This callback should be triggered when model is loaded.
      modelLoadedCallback: function () {
        if (model && propertyName) {
          model.removeObserver(propertyName, updateSlider);
          model.removePropertyDescriptionObserver(propertyName, updateSliderDisabledState);
        }
        scriptingAPI = interactivesController.getScriptingAPI();
        model = interactivesController.getModel();
        if (propertyName) {
          model.addPropertiesListener([propertyName], updateSlider);
          model.addPropertyDescriptionObserver(propertyName, updateSliderDisabledState);
        }

        bindTargets();

        if (propertyName) {
          updateSlider();
        }
      },

      // Returns view container (div).
      getViewContainer: function () {
        return $elem;
      },

      resize: function () {
        var remainingHeight,
            emSize = parseFloat($sliderHandle.css("font-size"));
        if (component.height !== "auto") {
          // Height calculation is more complex when height is different from
          // "auto". Calculate dynamically available height for slider itself.
          // Note that component.height refers to the height of the *whole*
          // component!
          remainingHeight = $elem.height();
          if (component.titlePosition === "top" || component.titlePosition === "bottom") {
            remainingHeight -= $title.outerHeight(true);
          }
          if ($label !== undefined) {
            remainingHeight -= $label.outerHeight(true);
          }
          $container.css("height", remainingHeight);
          $slider.css("top", 0.5 * remainingHeight);
          // Handle also requires dynamic styling.
          $sliderHandle.css("height", remainingHeight + emSize * 0.4);
          $sliderHandle.css("top", -0.5 * remainingHeight - emSize * 0.4);
        }

        if (component.titlePosition === "left" || component.titlePosition === "right") {
          $container.css({ width: $elem.width() - $title.outerWidth(true) - 0.5*emSize });
        }
      },

      enableLogging: function (logFunc) {
        var data = {id: component.id, label: component.label};
        if (propertyName) {
          data.property = propertyName;
        }
        var startTime;
        $slider.off('.logging');
        $slider.on('slidestart.logging', function (event, ui) {
          startTime = Date.now();
          data.startVal = data.minVal = data.maxVal = ui.value;
        });
        $slider.on('slide.logging', function (event, ui) {
          data.minVal = Math.min(data.minVal, ui.value);
          data.maxVal = Math.max(data.maxVal, ui.value);
        });
        $slider.on('slidestop.logging', function (event, ui) {
          data.endVal = ui.value;
          data.time = (Date.now() - startTime) / 1000;
          logFunc('SliderChanged', data);
        });
      },

      // Returns serialized component definition.
      serialize: function () {
        var result = $.extend(true, {}, component);

        if (!propertyName) {
          // No property binding. Just action script.
          // Update "initialValue" to represent current
          // value of the slider.
          result.initialValue = $slider.slider('value');
        }

        return result;
      }
    };

    initialize();

    // Return Public API object.
    return controller;
  };
});

define('common/views/select-box-view',[],function() {

  return function SelectBoxView(opts) {
    var id       = opts.id,
        options  = opts.options,
        label    = opts.label,
        labelOn  = opts.labelOn,
        onChange = opts.onChange,
        ignoreChangeEvent,
        $select,
        $wrapper;

    function changeHandler() {
      var index;

      if (ignoreChangeEvent) {
        // Ignore change event caused by the pulldown menu update. It prevents from infinite loop of
        // pulldown - property updates.
        ignoreChangeEvent = false;
        return;
      }
      index = $(this).prop('selectedIndex');
      onChange(options[index], index);
    }

    return {

      update: function(selection) {
        // Set flag indicating that change event should be ignored by our own change listener. It
        // prevents from infinite loop like: pulldown update => property update => pulldown update =>
        // ... It's necessary as selectOption() call below will trigger change event of original
        // select. It's used by selectBoxIt to update its view.
        ignoreChangeEvent = true;
        // Retrieve all of the SelectBoxIt methods and call selectOption(). Note that we have to call
        // .toString() as numeric values are interpreted as option index by selectBoxIt. See:
        // http://gregfranko.com/jquery.selectBoxIt.js/#Methods
        // Also note that we have to call trim, as otherwise selectBoxIt won't recognize passed
        // string as an option if there are unnecessary whitespace characters.
        $select.data("selectBox-selectBoxIt").selectOption(selection.toString().trim());
      },

      render: function(parent) {
        var $options = [],
            $option, $label, ulEms, arrowEms, textMaxWidth, boxWidth;

        $select = $('<select>');

        options.forEach(function(option) {
          $option = $('<option>').html(option.text);
          $options.push($option);
          if (option.disabled) {
            $option.prop("disabled", option.disabled);
          }
          if (option.selected) {
            $option.prop("selected", option.selected);
          }
          // allow pulldowns to have falsy values, such as 0.
          if (option.value !== undefined) {
            $option.prop("value", option.value);
          }
          $select.append($option);
        });

        $select.change(changeHandler);

        // First append label to wrapper, then <select>
        $wrapper = $('<div>').attr('id', id);
        if (label) {
          $label = $("<span>").text(label);
          $label.addClass("label");
          $label.addClass(labelOn === "top" ? "on-top" : "on-left");
          $wrapper.append($label);
        }
        $wrapper.append($select);

        // Must call selectBoxIt after appending to wrapper
        $select.selectBoxIt();
        this.$element = $select;

        $wrapper.find(".selectboxit").css("width", "auto");

        // SelectBoxIt assumes that all select boxes are always going to have a width
        // set in CSS (default 220px). This doesn't work for us, as we don't know how
        // wide the content is going to be. Instead we have to measure the needed width
        // of the internal ul list, and use that to define the width of the select box.
        //
        // This issue has been raised in SelectBoxIt:
        // https://github.com/gfranko/jquery.selectBoxIt.js/issues/129
        //
        // However, this is still problematic because we haven't added the element to
        // the page yet. This $().measure function allows us to embed the element hidden
        // on the page first to allow us to check the required width.

        // ems for a given pixel size
        function pxToEm(input) {
          var emSize = parseFloat(parent.css("font-size"));
          return input / emSize;
        }

        function width() {
          return this.width();
        }

        ulEms    = pxToEm($wrapper.measure(width, "ul", parent));
        arrowEms = pxToEm($wrapper.measure(width, ".selectboxit-arrow-container", parent));

        textMaxWidth = ulEms+"em";
        boxWidth  = (ulEms + arrowEms + 0.3)+"em";

        $wrapper.find(".selectboxit").css("width", boxWidth);
        $wrapper.find(".selectboxit-text").css("max-width", textMaxWidth);

        // set hidden select box dimensions too, for mobile devices
        $wrapper.find(".selectboxit-container select").css({
          width: boxWidth,
          height: "100%"
        });

        return $wrapper;
      },

      get $element() {
        return $wrapper;
      },

      refresh: function() {
        // grab the SelectBoxIt instance and call into it
        $select.data('selectBox-selectBoxIt').refresh();
      },

      destroy: function() {
        $select.data('selectBox-selectBoxIt').destroy();
        $wrapper.remove();
      }
    };
  };
});

/*global require, define, $ */

define('common/controllers/pulldown-controller',['common/controllers/interactive-metadata','common/validator','common/controllers/disablable','common/controllers/help-icon-support','common/views/select-box-view','common/jquery-plugins'],function () {

  var metadata        = require('common/controllers/interactive-metadata'),
      validator       = require('common/validator'),
      disablable      = require('common/controllers/disablable'),
      helpIconSupport = require('common/controllers/help-icon-support'),
      SelectBoxView   = require('common/views/select-box-view');

      require('common/jquery-plugins');

  return function PulldownController(component, interactivesController) {
        // Public API.
    var controller,
        model,
        scriptingAPI,
        // Logging function, can be injected by #enableLogging call.
        logAction,
        // Options definitions from component JSON definition.
        options,
        view,
        $element;

    function updatePulldown() {
      if (component.property !== undefined) {
        view.update(model.get(component.property));
      }
    }

    function updatePulldownDisabledState() {
      var description = model.getPropertyDescription(component.property);
      controller.setDisabled(description.getFrozen());
    }

    function logChange(optionSpec) {
      if (!logAction) return; // logging is not enabled
      var data = {id: component.id, selected: optionSpec.text};
      if (component.label) data.label = component.label;
      if (component.property) {
        data.property = component.property;
        data.value = optionSpec.value;
      }
      logAction('PulldownChanged', data);
    }

    function initialize() {
      var parent = interactivesController.interactiveContainer,
          i, len;

      model = interactivesController.getModel();
      scriptingAPI = interactivesController.getScriptingAPI();

      // Validate component definition, use validated copy of the properties.
      component = validator.validateCompleteness(metadata.pulldown, component);
      // Validate pulldown options too.
      options = component.options;
      for (i = 0, len = options.length; i < len; i++) {
        options[i] = validator.validateCompleteness(metadata.pulldownOption, options[i]);
      }

      view = new SelectBoxView({
        id: component.id,
        options: options,
        label: component.label,
        labelOn: component.labelOn,
        onChange: function(option) {
          if (option.action) {
            scriptingAPI.makeFunctionInScriptContext(option.action)();
          } else if (option.value !== undefined) {
            scriptingAPI.api.set(component.property, option.value);
          }
          logChange(option);
        }
      });

      $element = view.render(parent);

      $element
        .addClass("interactive-pulldown")
        .addClass("component");

      if (component.tooltip) {
        $element.attr("title", component.tooltip);
      }

      disablable(controller, component);
      helpIconSupport(controller, component, interactivesController.helpSystem);
    }

    // Public API.
    controller = {
      modelLoadedCallback: function () {
        scriptingAPI = interactivesController.getScriptingAPI();
        if (component.property !== undefined) {
          if (model) {
            model.removeObserver(component.property, updatePulldown);
            model.removePropertyDescriptionObserver(component.property, updatePulldownDisabledState);
          }
          model = interactivesController.getModel();
          // Register listener for property.
          model.addObserver(component.property, updatePulldown);
          model.addPropertyDescriptionObserver(component.property, updatePulldownDisabledState);
          // Perform initial pulldown setup.
        } else {
          model = interactivesController.getModel();
        }
        updatePulldown();
      },

      enableLogging: function (logFunc) {
        logAction = logFunc;
      },

      // Returns view container.
      getViewContainer: function () {
        return $element;
      },

      // Returns serialized component definition.
      serialize: function () {
        var i, len, $options;
        if (component.property === undefined) {
          // When property binding is not defined, we need to keep track
          // which option is currently selected.
          $options = $element.find('option');
          for (i = 0, len = options.length; i < len; i++) {
            if ($($options[i]).prop("selected")) {
              options[i].selected = true;
            } else {
              delete options[i].selected;
            }
          }
        }
        // Note that 'options' array above is a reference to component.options array.
        // Every thing is updated, return a copy.
        return $.extend(true, {}, component);
      }
    };

    initialize();

    // Return Public API object.
    return controller;
  };
});

/*global define, $*/

define('common/controllers/joystick-controller',['common/controllers/interactive-metadata','common/validator','common/controllers/disablable','common/controllers/help-icon-support'],function () {

  var metadata        = require('common/controllers/interactive-metadata'),
      validator       = require('common/validator'),
      disablable      = require('common/controllers/disablable'),
      helpIconSupport = require('common/controllers/help-icon-support');

  return function JoystickController(component, interactivesController) {
    var propertyName, actionFunc, initialValue,
        title, labels, displayValue, displayFunc,
        // View elements.
        $elem,
        $container,
        $title,
        $labelN,$labelS,$labelE,$labelW,
        $joystickBase,
        $joystickHandle,
        $valueText,
        model,
        scriptingAPI,
        // Public API object.
        controller,
        valueChanged,
        hasBeenSetUp = false,
        base = {}, joystick = {},

        // Updates joystick using model property. Used in modelLoadedCallback.
        // Make sure that this function is only called when:
        // a) model is loaded,
        // b) joystick is bound to some property.
        updateJoystick = function (firstTime) {
          var value = interactivesController.getModel().get(propertyName);
          if (firstTime) { // FIXME This disables any changes coming from outside of our component...
            moveJoystickTo(value);
          }
          if (displayValue) {
            $valueText.text(displayFunc(value));
          }
        },
        updateJoystickDisabledState = function () {
          var description = model.getPropertyDescription(propertyName);
          controller.setDisabled(description.getFrozen());
        };

    function bindTargets() {
      // Bind action or/and property, process other options.
      if (component.action) {
        // The 'action' property is a source of a function which assumes we pass it a parameter
        // called 'value'.
        actionFunc = scriptingAPI.makeFunctionInScriptContext('value', component.action);
        valueChanged = function(value) {
          actionFunc(value);
          if (displayValue) {
            $valueText.text(displayFunc(value));
          }
        };
      }

      if (propertyName) {
        valueChanged = function(value) {
          if (model) model.properties[propertyName] = value;
          if (displayValue) {
            $valueText.text(displayFunc(value));
          }
        };
      }

      if (displayValue) {
        displayFunc = scriptingAPI.makeFunctionInScriptContext('value', displayValue);
      }
    }

    function setup() {
      $joystickHandle.draggable({
          revert: false,    // set to true to have joystick slide back to center
          create: function() {
            init();
          },
          drag: function (event, ui) {
            var loc = limitXY(ui.position.left+joystick.halfWidth, ui.position.top+joystick.halfHeight);
            if (loc) {
              ui.position.left = loc.x-joystick.halfWidth;
              ui.position.top = loc.y-joystick.halfHeight;
            }

            // Normalize x/y to range from -1 to 1
            var rel_left = (ui.position.left - joystick.startLeft)/(base.radius - joystick.halfWidth);
            var rel_top = (joystick.startTop - ui.position.top)/(base.radius - joystick.halfHeight);
            joystickMoved(rel_left, rel_top);
          },
          stop: function () {
            //$('#coords').html("&nbsp;");
          }
      });
    }

    function init() {
      if (hasBeenSetUp) return;
      hasBeenSetUp = true;

      base.width = $joystickBase[0].offsetWidth;
      base.height = $joystickBase[0].offsetHeight;
      base.top = $joystickBase[0].offsetTop;
      base.left = $joystickBase[0].offsetLeft;
      base.center = [base.width / 2, base.height / 2];
      base.radius = base.width / 2;

      joystick.startLeft = parseInt($joystickHandle.css("left"));
      joystick.startTop = parseInt($joystickHandle.css("top"));

      $joystickHandle.data("startLeft", joystick.startLeft);
      $joystickHandle.data("startTop", joystick.startTop);

      joystick.halfWidth = $joystickHandle[0].offsetWidth/2;
      joystick.halfHeight = $joystickHandle[0].offsetHeight/2;
    }

    function limitXY(x, y) {
      var dist = distance([x, y], base.center);
      if (dist <= base.radius - joystick.halfWidth) {
        return null;
      } else {
        x = x - base.center[0];
        y = y - base.center[1];
        var radians = Math.atan2(y, x);
        return {
          x: Math.cos(radians) * (base.radius-joystick.halfWidth) + base.center[0],
          y: Math.sin(radians) * (base.radius-joystick.halfWidth) + base.center[1]
        };
      }
    }

    function distance(dot1, dot2) {
      var x1 = dot1[0],
          y1 = dot1[1],
          x2 = dot2[0],
          y2 = dot2[1];
      return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
    }

    function joystickMoved(x, y) {
      var magnitude = Math.sqrt(x*x + y*y) * component.scale,
          direction = (Math.atan2(y, x) + 2*Math.PI) % (2*Math.PI),
          data = {magnitude: magnitude, direction: direction};

      // Send the new value
      if (valueChanged) valueChanged(data);
    }

    function moveJoystickTo(value) {
      // Set the joystick in the right position
      var mag = (base.radius - joystick.halfWidth) * value.magnitude / component.scale, // Normalized to -radius to radius
          dx = mag * Math.cos(value.direction),
          dy = mag * Math.sin(value.direction),
          startLeft = base.center[0] - joystick.halfWidth + dx,
          startTop = base.center[1] - joystick.halfWidth - dy; // invert the y direction

      $joystickHandle.css({top: startTop, left: startLeft});
    }

    function initialize() {
      //
      // Initialize.
      //
      scriptingAPI = interactivesController.getScriptingAPI();
      model = interactivesController.getModel();
      // Validate component definition, use validated copy of the properties.
      component = validator.validateCompleteness(metadata.joystick, component);
      propertyName = component.property;
      initialValue = component.initialValue;
      title = component.title;
      labels = component.labels;
      displayValue = component.displayValue;

      model = interactivesController.getModel();

      if (propertyName === undefined && initialValue === undefined) initialValue = { magnitude: 0, direction: 0 };

      // Setup view.
      // <div id="interactive-joystick">
      //   <p class="title">Wind</p>
      //   <div id="joystick-base">
      //       <div id="joystick-handle"></div>
      //   </div>
      //   <span id="label n">N</span>
      //   <span id="label e">E</span>
      //   <span id="label s">S</span>
      //   <span id="label w">W</span>
      //   <div id="joystick-value">0 MPH</div>
      // </div>
      $container = $('<div class="container">');
      $title = $('<div class="title">' + title + '</div>');

      $joystickBase = $('<div class="base">').attr('id', component.id);
      $joystickHandle = $('<div class="handle">');
      $joystickHandle.appendTo($joystickBase);

      $labelN = $('<div class="label n">' + labels.n + '</div>');
      $labelS = $('<div class="label s">' + labels.s + '</div>');
      $labelW = $('<div class="label w">' + labels.w + '</div>');
      $labelE = $('<div class="label e">' + labels.e + '</div>');

      $container.append($labelN)
                .append($labelW)
                .append($joystickBase)
                .append($labelE)
                .append($labelS);

      $elem = $('<div class="interactive-joystick">')
                .append($title)
                .append($container);

      // Each interactive component has to have class "component".
      $elem.addClass("component");

      $valueText = $('<div class="value"></div>');
      $elem.append($valueText);

      if (component.tooltip) {
        $elem.attr("title", component.tooltip);
      }

      disablable(controller, component);
      helpIconSupport(controller, component, interactivesController.helpSystem);

      // Apply custom width and height settings.
      // Also not that we set dimensions of the most outer container, not slider.
      // Slider itself will always follow dimensions of container DIV.
      // We have to do it that way to ensure that labels refer correct dimensions.
      $elem.css({
        "width": component.width,
        "height": component.height
      });
      // Call resize function to support complex resizing when height is different from "auto".
      controller.resize(true);

      // Finally set the initial value if it's provided.
      if (initialValue !== undefined && initialValue !== null) {
        moveJoystickTo(initialValue);
        if (displayValue) {
          $valueText.text(displayFunc(initialValue));
        }
      }
    }

    // Public API.
    controller = {
      // This callback should be triggered when model is loaded.
      modelLoadedCallback: function () {
        if (model && propertyName) {
          model.removeObserver(propertyName, updateJoystick);
          model.removePropertyDescriptionObserver(propertyName, updateJoystickDisabledState);
        }
        scriptingAPI = interactivesController.getScriptingAPI();
        model = interactivesController.getModel();
        if (propertyName) {
          model.addPropertiesListener([propertyName], updateJoystick);
          model.addPropertyDescriptionObserver(propertyName, updateJoystickDisabledState);
        }

        bindTargets();

        setup();

        if (propertyName) {
          updateJoystick(true);
        }
      },

      // Returns view container (div).
      getViewContainer: function () {
        return $elem;
      },

      resize: function (fromSetup) {
        if (fromSetup) return;
        var width = $elem[0].clientWidth,
            height = $elem[0].clientHeight,
            emSize = parseFloat($elem.css('font-size')),
            baseHeight = height - 4.2*emSize, // subtract for title, value text, N-label, S-label and 0.1em padding on both top and bottom\
            labelsWidth = $labelE.outerWidth(true) + $labelW.outerWidth(true),
            baseWidth = width - labelsWidth,
            labelsOverflow = 0;

        if (baseWidth < 2*emSize) {
          labelsOverflow = Math.abs(2*emSize - baseWidth);
          baseWidth = 2*emSize;
        }

        var baseSize = baseWidth < baseHeight ? baseWidth : baseHeight,
            handleSize = baseSize * 0.3,
            containerHeight = baseSize + 2.2*emSize,
            centerX;

        if (labelsOverflow > 0) {
          var adjPct = (labelsWidth - labelsOverflow - 0.1*emSize) / labelsWidth;
          centerX = $labelW.outerWidth(true) * adjPct + 0.1*emSize + baseSize/2;
        } else {
          centerX = $labelW.outerWidth(true) + 0.1*emSize + baseSize/2;
        }

        $container.css({ width: width, height: containerHeight });

        $labelE.css({ left: centerX + baseSize/2 + 0.1*emSize });
        $labelW.css({ left: centerX - baseSize/2 - 0.1*emSize - $labelW[0].clientWidth });
        $labelN.css({ left: centerX - $labelN.outerWidth(true)/2 });
        $labelS.css({ left: centerX - $labelS.outerWidth(true)/2 });

        $joystickBase.css({ height: baseSize, width: baseSize, left: centerX - baseSize/2, top: 1.1*emSize, borderRadius: baseSize });
        $joystickHandle.css({ height: handleSize, width: handleSize, borderRadius: baseSize, left: baseSize/2 - handleSize/2, top: baseSize/2 - handleSize/2 });

        // update joystick cached info
        hasBeenSetUp = false;
        init();

        // update the actual joystick position to match the current value
        var value = interactivesController.getModel().get(propertyName);
        moveJoystickTo(value);
      },

      // Returns serialized component definition.
      serialize: function () {
        var result = $.extend(true, {}, component);

        if (!propertyName) {
          // No property binding. Just action script.
          // Update "initialValue" to represent current
          // value of the slider.
          result.initialValue = {magnitude: 0, direction: 0};
        }

        return result;
      }
    };

    initialize();

    // Return Public API object.
    return controller;
  };
});

/*global define, $*/

define('common/controllers/color-indicator-controller',['common/controllers/interactive-metadata','common/validator','common/controllers/help-icon-support'],function () {

  var metadata        = require('common/controllers/interactive-metadata'),
      validator       = require('common/validator'),
      helpIconSupport = require('common/controllers/help-icon-support');

  return function ColorIndicatorController(component, interactivesController) {
    var propertyName, initialValue,
        title, colorFunc,
        // View elements.
        $elem,
        $title,
        $swatch,
        model,
        scriptingAPI,
        // Public API object.
        controller,

        // Updates joystick using model property. Used in modelLoadedCallback.
        // Make sure that this function is only called when:
        // a) model is loaded,
        // b) joystick is bound to some property.
        updateColorIndicator = function () {
          var value = interactivesController.getModel().get(propertyName);

          // Set the new color
          var color = colorFunc(value); // "hsl("+value+",100%,50%)";
          $swatch.css("background-color", color);
        };

    function initialize() {
      //
      // Initialize.
      //
      scriptingAPI = interactivesController.getScriptingAPI();
      model = interactivesController.getModel();
      // Validate component definition, use validated copy of the properties.
      component = validator.validateCompleteness(metadata.colorIndicator, component);
      propertyName = component.property;
      initialValue = component.initialValue;
      title = component.title;

      colorFunc = scriptingAPI.makeFunctionInScriptContext('value', component.colorValue);

      model = interactivesController.getModel();

      $title = $('<div class="title">' + title + '</div>');
      $swatch = $('<div class="swatch"></div>');

      $elem = $('<div class="interactive-color-indicator">')
                .append($title)
                .append($swatch);

      // Each interactive component has to have class "component".
      $elem.addClass("component");

      if (component.tooltip) {
        $elem.attr("title", component.tooltip);
      }

      helpIconSupport(controller, component, interactivesController.helpSystem);

      // Apply custom width and height settings.
      // Also not that we set dimensions of the most outer container, not slider.
      // Slider itself will always follow dimensions of container DIV.
      // We have to do it that way to ensure that labels refer correct dimensions.
      $elem.css({
        "width": component.width,
        "height": component.height
      });
      // Call resize function to support complex resizing when height is different from "auto".
      controller.resize();
    }

    // Public API.
    controller = {
      // This callback should be triggered when model is loaded.
      modelLoadedCallback: function () {
        if (model && propertyName) {
          model.removeObserver(propertyName, updateColorIndicator);
        }
        scriptingAPI = interactivesController.getScriptingAPI();
        model = interactivesController.getModel();
        if (propertyName) {
          model.addPropertiesListener([propertyName], updateColorIndicator);
        }

        if (propertyName) {
          updateColorIndicator();
        }
      },

      // Returns view container (div).
      getViewContainer: function () {
        return $elem;
      },

      resize: function () {
      },

      // Returns serialized component definition.
      serialize: function () {
        return $.extend(true, {}, component);
      }
    };

    initialize();

    // Return Public API object.
    return controller;
  };
});

/*global define */

// Simple wrapper for cloning and restoring hash of arrays.
// Such structure is widely used in md2d engine for keeping
// state of various objects (like atoms and obstacles).
// Use it in the following way:
// var obj = saveRestoreWrapper(hashOfArrays)
// var state = obj.clone();
// (...)
// obj.restore(state);

define('common/models/engines/clone-restore-wrapper',['require','arrays'],function (require) {
  // Dependencies.
  var arrays = require('arrays');

  return function CloneRestoreWrapper(hashOfArrays, options) {
    options = options || {};

    // Public API.
    var ret = {
      // Clone hash of arrays
      clone: function() {
        var copy = {},
            prop;

        for (prop in hashOfArrays) {
          if (hashOfArrays.hasOwnProperty(prop)) {
            copy[prop] = arrays.clone(hashOfArrays[prop]);
          }
        }

        return copy;
      }
    };

    // Restore internal arrays using saved state. 2 paths, depending on options.padArraysWithZeroes
    if (options.padArraysWithZeroes) {
      ret.restore = function(state) {
        var prop, target, i, j;

        for (prop in hashOfArrays) {
          if (hashOfArrays.hasOwnProperty(prop)) {
            target = hashOfArrays[prop];
            arrays.copy(state[prop], target);
            for (i = state[prop].length, j = target.length; i < j; i++) {
              target[i] = 0;
            }
          }
        }
      };
    } else {
      ret.restore = function(state) {
        var prop;

        for (prop in hashOfArrays) {
          if (hashOfArrays.hasOwnProperty(prop)) {
            arrays.copy(state[prop], hashOfArrays[prop]);
          }
        }
      };
    }

    return ret;
  };

});

/*global define: true */
/** Provides a few simple helper functions for converting related unit types.

    This sub-module doesn't do unit conversion between compound unit types (e.g., knowing that kg*m/s^2 = N)
    only simple scaling between units measuring the same type of quantity.
*/

// Prefer the "per" formulation to the "in" formulation.
//
// If KILOGRAMS_PER_AMU is 1.660540e-27 we know the math is:
// "1 amu * 1.660540e-27 kg/amu = 1.660540e-27 kg"
// (Whereas the "in" forumulation might be slighty more error prone:
// given 1 amu and 6.022e-26 kg in an amu, how do you get kg again?)

// These you might have to look up...

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. RequireJS Optimizer will strip out this if statement.


define('models/md2d/models/engine/constants/units',['require','exports','module'],function (require, exports, module) {

  var KILOGRAMS_PER_DALTON  = 1.660540e-27,
      COULOMBS_PER_ELEMENTARY_CHARGE = 1.602177e-19,

      // 1 eV = 1 e * 1 V = (COULOMBS_PER_ELEMENTARY_CHARGE) C * 1 J/C
      JOULES_PER_EV = COULOMBS_PER_ELEMENTARY_CHARGE,

      // though these are equally important!
      SECONDS_PER_FEMTOSECOND = 1e-15,
      METERS_PER_NANOMETER    = 1e-9,
      ANGSTROMS_PER_NANOMETER = 10,
      GRAMS_PER_KILOGRAM      = 1000,

      types = {
        TIME: "time",
        LENGTH: "length",
        MASS: "mass",
        ENERGY: "energy",
        ENTROPY: "entropy",
        CHARGE: "charge",
        INVERSE_QUANTITY: "inverse quantity",

        FARADS_PER_METER: "farads per meter",
        METERS_PER_FARAD: "meters per farad",

        FORCE: "force",
        VELOCITY: "velocity",

        // unused as of yet
        AREA: "area",
        PRESSURE: "pressure"
      },

    unit,
    ratio,
    convert;

  /**
    In each of these units, the reference type we actually use has value 1, and conversion
    ratios for the others are listed.
  */
  exports.unit = unit = {

    FEMTOSECOND: { name: "femtosecond", value: 1,                       type: types.TIME },
    SECOND:      { name: "second",      value: SECONDS_PER_FEMTOSECOND, type: types.TIME },

    NANOMETER:   { name: "nanometer", value: 1,                           type: types.LENGTH },
    ANGSTROM:    { name: "Angstrom",  value: 1 * ANGSTROMS_PER_NANOMETER, type: types.LENGTH },
    METER:       { name: "meter",     value: 1 * METERS_PER_NANOMETER,    type: types.LENGTH },

    DALTON:   { name: "Dalton",   value: 1,                                             type: types.MASS },
    GRAM:     { name: "gram",     value: 1 * KILOGRAMS_PER_DALTON * GRAMS_PER_KILOGRAM, type: types.MASS },
    KILOGRAM: { name: "kilogram", value: 1 * KILOGRAMS_PER_DALTON,                      type: types.MASS },

    MW_ENERGY_UNIT: {
      name: "MW Energy Unit (Dalton * nm^2 / fs^2)",
      value: 1,
      type: types.ENERGY
    },

    JOULE: {
      name: "Joule",
      value: KILOGRAMS_PER_DALTON *
             METERS_PER_NANOMETER * METERS_PER_NANOMETER *
             (1/SECONDS_PER_FEMTOSECOND) * (1/SECONDS_PER_FEMTOSECOND),
      type: types.ENERGY
    },

    EV: {
      name: "electron volt",
      value: KILOGRAMS_PER_DALTON *
              METERS_PER_NANOMETER * METERS_PER_NANOMETER *
              (1/SECONDS_PER_FEMTOSECOND) * (1/SECONDS_PER_FEMTOSECOND) *
              (1/JOULES_PER_EV),
      type: types.ENERGY
    },

    EV_PER_KELVIN:     { name: "electron volts per Kelvin", value: 1,                 type: types.ENTROPY },
    JOULES_PER_KELVIN: { name: "Joules per Kelvin",         value: 1 * JOULES_PER_EV, type: types.ENTROPY },

    ELEMENTARY_CHARGE: { name: "elementary charge", value: 1,                             type: types.CHARGE },
    COULOMB:           { name: "Coulomb",           value: COULOMBS_PER_ELEMENTARY_CHARGE, type: types.CHARGE },

    INVERSE_MOLE: { name: "inverse moles", value: 1, type: types.INVERSE_QUANTITY },

    FARADS_PER_METER: { name: "Farads per meter", value: 1, type: types.FARADS_PER_METER },

    METERS_PER_FARAD: { name: "meters per Farad", value: 1, type: types.METERS_PER_FARAD },

    MW_FORCE_UNIT: {
      name: "MW force units (Dalton * nm / fs^2)",
      value: 1,
      type: types.FORCE
    },

    NEWTON: {
      name: "Newton",
      value: 1 * KILOGRAMS_PER_DALTON * METERS_PER_NANOMETER * (1/SECONDS_PER_FEMTOSECOND) * (1/SECONDS_PER_FEMTOSECOND),
      type: types.FORCE
    },

    EV_PER_NM: {
      name: "electron volts per nanometer",
      value: 1 * KILOGRAMS_PER_DALTON * METERS_PER_NANOMETER * METERS_PER_NANOMETER *
             (1/SECONDS_PER_FEMTOSECOND) * (1/SECONDS_PER_FEMTOSECOND) *
             (1/JOULES_PER_EV),
      type: types.FORCE
    },

    MW_VELOCITY_UNIT: {
      name: "MW velocity units (nm / fs)",
      value: 1,
      type: types.VELOCITY
    },

    METERS_PER_SECOND: {
      name: "meters per second",
      value: 1 * METERS_PER_NANOMETER * (1 / SECONDS_PER_FEMTOSECOND),
      type: types.VELOCITY
    }

  };


  /** Provide ratios for conversion of one unit to an equivalent unit type.

     Usage: ratio(units.GRAM, { per: units.KILOGRAM }) === 1000
            ratio(units.GRAM, { as: units.KILOGRAM }) === 0.001
  */
  exports.ratio = ratio = function(from, to) {
    var checkCompatibility = function(fromUnit, toUnit) {
      if (fromUnit.type !== toUnit.type) {
        throw new Error("Attempt to convert incompatible type '" + fromUnit.name + "'' to '" + toUnit.name + "'");
      }
    };

    if (to.per) {
      checkCompatibility(from, to.per);
      return from.value / to.per.value;
    } else if (to.as) {
      checkCompatibility(from, to.as);
      return to.as.value / from.value;
    } else {
      throw new Error("units.ratio() received arguments it couldn't understand.");
    }
  };

  /** Scale 'val' to a different unit of the same type.

    Usage: convert(1, { from: unit.KILOGRAM, to: unit.GRAM }) === 1000
  */
  exports.convert = convert = function(val, fromTo) {
    var from = fromTo && fromTo.from,
        to   = fromTo && fromTo.to;

    if (!from) {
      throw new Error("units.convert() did not receive a \"from\" argument");
    }
    if (!to) {
      throw new Error("units.convert() did not receive a \"to\" argument");
    }

    return val * ratio(to, { per: from });
  };
});

/*global define: true */
/*jslint loopfunc: true */

/** A list of physical constants. To access any given constant, require() this module
    and call the 'as' method of the desired constant to get the constant in the desired unit.

    This module also provides a few helper functions for unit conversion.

    Usage:
      var constants = require('./constants'),

          ATOMIC_MASS_IN_GRAMS = constants.ATOMIC_MASS.as(constants.unit.GRAM),

          GRAMS_PER_KILOGRAM = constants.ratio(constants.unit.GRAM, { per: constants.unit.KILOGRAM }),

          // this works for illustration purposes, although the preferred method would be to pass
          // constants.unit.KILOGRAM to the 'as' method:

          ATOMIC_MASS_IN_KILOGRAMS = constants.convert(ATOMIC_MASS_IN_GRAMS, {
            from: constants.unit.GRAM,
            to:   constants.unit.KILOGRAM
          });
*/

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. RequireJS Optimizer will strip out this if statement.


define('models/md2d/models/engine/constants/index',['require','exports','module','./units'],function (require, exports, module) {

  var units = require('./units'),
      unit  = units.unit,
      ratio = units.ratio,
      convert = units.convert,

      constants = {

        ELEMENTARY_CHARGE: {
          value: 1,
          unit: unit.ELEMENTARY_CHARGE
        },

        ATOMIC_MASS: {
          value: 1,
          unit: unit.DALTON
        },

        BOLTZMANN_CONSTANT: {
          value: 1.380658e-23,
          unit: unit.JOULES_PER_KELVIN
        },

        AVAGADRO_CONSTANT: {
          // N_A is numerically equal to Dalton per gram
          value: ratio( unit.DALTON, { per: unit.GRAM }),
          unit: unit.INVERSE_MOLE
        },

        PERMITTIVITY_OF_FREE_SPACE: {
          value: 8.854187e-12,
          unit: unit.FARADS_PER_METER
        }
      },

      constantName, constant;


  // Derived units
  constants.COULOMB_CONSTANT = {
    value: 1 / (4 * Math.PI * constants.PERMITTIVITY_OF_FREE_SPACE.value),
    unit: unit.METERS_PER_FARAD
  };

  // Exports

  exports.unit = unit;
  exports.ratio = ratio;
  exports.convert = convert;

  // Require explicitness about units by publishing constants as a set of objects with only an 'as' property,
  // which will return the constant in the specified unit.

  for (constantName in constants) {
    if (constants.hasOwnProperty(constantName)) {
      constant = constants[constantName];

      exports[constantName] = (function(constant) {
        return {
          as: function(toUnit) {
            return units.convert(constant.value, { from: constant.unit, to: toUnit });
          }
        };
      }(constant));
    }
  }
});

/*global define: false*/
define('models/md2d/models/engine/utils',['require','arrays'],function(require) {

  var arrays = require('arrays');

  /**
    Extend all arrays in arrayContainer to `newLength`. Here, arrayContainer is expected to be `atoms`
    `elements`, `radialBonds`, etc. arrayContainer might be an array or an object.
    TODO: this is just interim solution, in the future only objects will be expected.
  */
  return {
    extendArrays: function(arrayContainer, newLength) {
      var i, len;
      if (Array.isArray(arrayContainer)) {
        // Array of arrays.
        for (i = 0, len = arrayContainer.length; i < len; i++) {
          if (arrays.isArray(arrayContainer[i]))
            arrayContainer[i] = arrays.extend(arrayContainer[i], newLength);
        }
      } else {
        // Object with arrays defined as properties.
        for (i in arrayContainer) {
          if(arrayContainer.hasOwnProperty(i)) {
            if (arrays.isArray(arrayContainer[i]))
              arrayContainer[i] = arrays.extend(arrayContainer[i], newLength);
          }
        }
      }
    }
  };
});

/*global define: true */

// Tiny module which contains definition of preferred
// array types used across whole Lab project.
// It checks whether typed arrays are available and type of browser
// (as typed arrays are slower in Safari).

define('common/array-types',['require','arrays'],function (require) {
  // Dependencies.
  var arrays = require('arrays'),

      // Check for Safari. Typed arrays are faster almost everywhere ... except Safari.
      notSafari = (function() {
        // Node.js?
        if (typeof navigator === 'undefined')
          return true;
        // Safari?
        var safarimatch  = / AppleWebKit\/([0123456789.+]+) \(KHTML, like Gecko\) Version\/([0123456789.]+) (Safari)\/([0123456789.]+)/,
            match = navigator.userAgent.match(safarimatch);
        return !match || !match[3];
      }()),

      useTyped = arrays.typed && notSafari;

  // Return all available types of arrays.
  // If you need to use new type, declare it here.
  return {
    floatType:  useTyped ? 'Float64Array' : 'regular',
    int32Type:  useTyped ? 'Int32Array'   : 'regular',
    int16Type:  useTyped ? 'Int16Array'   : 'regular',
    int8Type:   useTyped ? 'Int8Array'    : 'regular',
    uint16Type: useTyped ? 'Uint16Array'  : 'regular',
    uint8Type:  useTyped ? 'Uint8Array'   : 'regular'
  };

});

/*global define: false */

define('models/md2d/models/metadata',[],function() {

  return {
    mainProperties: {
      type: {
        defaultValue: "md2d",
        immutable: true
      },
      isBeingEdited: {
        defaultValue: false,
        serialize: false
      },
      imagePath: {
        defaultValue: "",
        immutable: true
      },
      minX: {
        serialize: false
      },
      maxX: {
        serialize: false
      },
      minY: {
        serialize: false
      },
      maxY: {
        serialize: false
      },
      width: {
        defaultValue: 10,
        unitType: "length",
        immutable: true
      },
      height: {
        defaultValue: 10,
        unitType: "length",
        immutable: true
      },
      unitsScheme: {
        defaultValue: "md2d"
      },
      lennardJonesForces: {
        defaultValue: true,
        storeInTickHistory: true
      },
      coulombForces: {
        defaultValue: true,
        storeInTickHistory: true
      },
      temperatureControl: {
        defaultValue: false,
        storeInTickHistory: true
      },
      targetTemperature: {
        defaultValue: 300,
        unitType: "temperature",
        storeInTickHistory: true
      },
      modelSampleRate: {
        defaultValue: "default"
      },
      gravitationalField: {
        defaultValue: false,
        unitType: "acceleration",
        storeInTickHistory: true
      },
      timeStep: {
        defaultValue: 1,
        unitType: "time",
        storeInTickHistory: true
      },
      dielectricConstant: {
        defaultValue: 1
      },
      realisticDielectricEffect: {
        defaultValue: true
      },
      solventForceFactor: {
        defaultValue: 1.25
      },
      solventForceType: {
        //  0 - vacuum.
        //  1 - water.
        // -1 - oil.
        defaultValue: 0
      },
      // Additional force applied to amino acids that depends on distance from the center of mass. It affects
      // only AAs which are pulled into the center of mass (to stabilize shape of the protein).
      // 'additionalSolventForceMult'      - maximum multiplier applied to solvent force when AA is in the center of mass.
      // 'additionalSolventForceThreshold' - maximum distance from the center of mass which triggers this increase of the force.
      // The additional force is described by the linear function of the AA distance from the center of mass
      // that passes through two points:
      // (0, additionalSolventForceMult) and (additionalSolventForceThreshold, 1).
      additionalSolventForceMult: {
        defaultValue: 4
      },
      additionalSolventForceThreshold: {
        defaultValue: 10,
        unitType: "length"
      },
      polarAAEpsilon: {
        defaultValue: -2
      },
      viscosity: {
        defaultValue: 1,
        storeInTickHistory: true
      },
      timeStepsPerTick: {
        defaultValue: 50,
        storeInTickHistory: true
      },
      DNAState: {
        defaultValue: "dna"
      },
      DNA: {
        defaultValue: "",
        validate: function (value) {
          if (/[agtc]/.test(value)) {
            value = value.toUpperCase();
          }
          if (/[^AGTC]/.test(value)) {
            throw new Error("DNA code on sense strand can be defined using only A, G, T or C characters.");
          }
          return value;
        }
      },
      DNAMutations: {
        defaultValue: true
      },
      useQuantumDynamics: {
        defaultValue: false
      },
      useChemicalReactions: {
        defaultValue: false
      },
      useDuration: {
        defaultValue: 'codap',
        storeInTickHistory: false,
        validate: function(value) {
          if (value === true || value === false || value === 'codap') {
            return value;
          }
          throw new Error("Invalid 'useDuration' value: " + value);
        }
      },
      requestedDuration: {
        defaultValue: null,
        storeInTickHistory: false
      },
      skipPECheckOnAddAtom: {
        defaultValue: false
      }
    },

    viewOptions: {
      viewPortWidth: {
        unitType: "length",
        immutable: true
      },
      viewPortHeight: {
        unitType: "length",
        immutable: true
      },
      viewPortZoom: {
        defaultValue: 1
      },
      viewPortX: {
        unitType: "length"
      },
      viewPortY: {
        unitType: "length"
      },
      viewPortDrag: {
        // Supported values:
        // - true  -> dragging is enabled.
        // - "x"   -> dragging is limited only to X axis.
        // - "y"   -> dragging is limited only yo Y axis.
        // - false -> dragging is disabled.
        defaultValue: false
      },
      backgroundColor: {
        defaultValue: "#eeeeee"
      },
      showClock: {
        defaultValue: true,
        storeInTickHistory: true
      },
      markColor: {
        defaultValue: "#f8b500"
      },
      atomRadiusScale: {
        defaultValue: 1
      },
      keShading: {
        defaultValue: false,
        storeInTickHistory: true
      },
      keShadingMinEnergy: {
        // Kinetic energy of an atom which is lower boundary for KE shading (white shading).
        defaultValue: 0,
        storeInTickHistory: true
      },
      keShadingMaxEnergy: {
        // Kinetic energy of an atom which is upper boundary for KE shading (red shading).
        defaultValue: 0.2,
        storeInTickHistory: true
      },
      chargeShading: {
        defaultValue: false,
        storeInTickHistory: true
      },
      chargeShadingStyle: {
        // "biology" (+ blue, - red) or "chemistry" (+ red, - blue).
        defaultValue: "biology",
        storeInTickHistory: true
      },
      aminoAcidColorScheme: {
        defaultValue: "hydrophobicity"
      },
      aminoAcidLabels: {
        defaultValue: true,
      },
      useThreeLetterCode: {
        // Amino acid labels type - single letter (false) or three letters (true).
        defaultValue: true
      },
      showChargeSymbols: {
        defaultValue: true
      },
      showVDWLines: {
        defaultValue: false,
        storeInTickHistory: true
      },
      VDWLinesCutoff: {
        defaultValue: "medium"
      },
      showVelocityVectors: {
        defaultValue: false,
        storeInTickHistory: true
      },
      showForceVectors: {
        defaultValue: false,
        storeInTickHistory: true
      },
      showElectricField: {
        defaultValue: false,
        storeInTickHistory: true
      },
      electricFieldDensity: {
        defaultValue: 18, // it means 18 arrows per row
        storeInTickHistory: true
      },
      electricFieldColor: {
        // "auto" means color contrasting to background, black or white.
        // However any custom color can be specified.
        defaultValue: "auto"
      },
      showAtomTrace: {
        storeInTickHistory: true,
        defaultValue: false
      },
      atomTraceId: {
        storeInTickHistory: true,
        defaultValue: 0
      },
      images: {
        defaultValue: []
      },
      imageMapping: {
        defaultValue: {}
      },
      textBoxes: {
        defaultValue: []
      },
      xlabel: {
        defaultValue: false
      },
      ylabel: {
        defaultValue: false
      },
      xunits: {
        defaultValue: false
      },
      yunits: {
        defaultValue: false
      },
      controlButtons: {
        defaultValue: "play"
      },
      controlButtonStyle: {
        defaultValue: "video",
        propertyChangeInvalidates: false,
        // expectation is that this will be set by the interactive
        serialize: false
      },
      gridLines: {
        defaultValue: false
      },
      atomNumbers: {
        defaultValue: false
      },
      enableAtomTooltips: {
        defaultValue: false
      },
      enableKeyboardHandlers: {
        defaultValue: true
      },
      atomTraceColor: {
        defaultValue: "#6913c5"
      },
      velocityVectors: {
        defaultValue: {
          color: "#000",
          width: 0.01,
          length: 2
        }
      },
      forceVectors: {
        defaultValue: {
          color: "#169C30",
          width: 0.01,
          length: 2
        }
      },
      forceVectorsDirectionOnly: {
        defaultValue: false
      },
      onAtomDrag: {
        // Atom dragging can either start translation or rotation of the molecule.
        // Behavior which is not default can be activated if user holds Alt / Opt key while dragging.
        // Available options: 'translate', 'rotate'.
        defaultValue: 'translate'
      }
    },

    atom: {
      // Required properties:
      x: {
        required: true,
        unitType: "length"
      },
      y: {
        required: true,
        unitType: "length"
      },
      // Optional properties:
      element: {
        defaultValue: 0
      },
      vx: {
        defaultValue: 0,
        unitType: "velocity"
      },
      vy: {
        defaultValue: 0,
        unitType: "velocity"
      },
      ax: {
        defaultValue: 0,
        unitType: "acceleration",
        serialize: false
      },
      ay: {
        defaultValue: 0,
        unitType: "acceleration",
        serialize: false
      },
      charge: {
        defaultValue: 0,
        unitType: "charge"
      },
      friction: {
        defaultValue: 0,
        unitType: "dampingCoefficient"
      },
      radical: {
        defaultValue: 0
      },
      visible: {
        // Note that it also accepts fractional values, e.g. 0.5 (=> atom will be semi-transparent).
        defaultValue: 1
      },
      pinned: {
        defaultValue: 0
      },
      marked: {
        defaultValue: 0
      },
      draggable: {
        defaultValue: 0
      },
      draggableWhenStopped: {
        defaultValue: 1
      },
      // Read-only values, can be set only by engine:
      radius: {
        readOnly: true,
        unitType: "length",
        serialize: false
      },
      px: {
        readOnly: true,
        unitType: "momentum",
        serialize: false
      },
      py: {
        readOnly: true,
        unitType: "momentum",
        serialize: false
      },
      speed: {
        readOnly: true,
        unitType: "velocity",
        serialize: false
      },
      mass: {
        // Mass is defined per element, but this is a convenience shortcut for
        // quick access to mass of the given atom.
        readOnly: true,
        unitType: "mass",
        serialize: false
      },
      excitation: {
        // [Quantum Dynamics plugin]
      },
      sharedElectrons: {
        // [Chemical Reactions plugin]
      }
    },

    element: {
      mass: {
        defaultValue: 120,
        unitType: "mass"
      },
      sigma: {
        defaultValue: 0.3,
        unitType: "length"
      },
      epsilon: {
        defaultValue: -0.1,
        unitType: "energy"
      },
      radius: {
        unitType: "length",
        readOnly: true,
        serialize: false
      },
      color: {
        defaultValue: -855310
      }
    },

    pairwiseLJProperties: {
      element1: {
        defaultValue: 0
      },
      element2: {
        defaultValue: 0
      },
      sigma: {
        unitType: "length"
      },
      epsilon: {
        unitType: "energy"
      }
    },

    obstacle: {
      // Required properties:
      width: {
        unitType: "length",
        required: true
      },
      height: {
        unitType: "length",
        required: true
      },
      // Optional properties:
      x: {
        defaultValue: 0,
        unitType: "length"
      },
      y: {
        defaultValue: 0,
        unitType: "length"
      },
      mass: {
        defaultValue: Infinity,
        unitType: "mass"
      },
      vx: {
        defaultValue: 0,
        unitType: "velocity"
      },
      vy: {
        defaultValue: 0,
        unitType: "velocity"
      },
      // Externally applied horizontal acceleration
      externalAx: {
        defaultValue: 0,
        unitType: "acceleration"
      },
      // Externally applied vertical acceleration
      externalAy: {
        defaultValue: 0,
        unitType: "acceleration"
      },
      // Whether to render arrows for the externally applied acceleration externalAx and externalAy
      displayExternalAcceleration: {
        defaultValue: true
      },
      // Damping coefficient per mass unit (= acceleration / velocity = 1 / time)
      friction: {
        defaultValue: 0,
        unitType: "inverseTime"
      },
      // Pressure probe, west side.
      westProbe: {
        defaultValue: false
      },
      // Final value of pressure in Bars.
      westProbeValue: {
        unitType: "pressure",
        readOnly: true,
        serialize: false
      },
      // Pressure probe, north side.
      northProbe: {
        defaultValue: false
      },
      // Final value of pressure in Bars.
      northProbeValue: {
        unitType: "pressure",
        readOnly: true,
        serialize: false
      },
      // Pressure probe, east side.
      eastProbe: {
        defaultValue: false
      },
      // Final value of pressure in Bars.
      eastProbeValue: {
        unitType: "pressure",
        readOnly: true,
        serialize: false
      },
      // Pressure probe, south side.
      southProbe: {
        defaultValue: false
      },
      // Final value of pressure in Bars.
      southProbeValue: {
        unitType: "pressure",
        readOnly: true,
        serialize: false
      },
      // View options.
      color:{
        defaultValue: "rgb(128,128,128)"
      },
      visible: {
        defaultValue: true
      }
    },

    shape: {
      // Required properties:
      type: {
        defaultValue: "rectangle",
        required: true
      },
      width: {
        unitType: "length",
        required: true
      },
      height: {
        unitType: "length",
        required: true
      },
      // Optional properties:
      x: {
        defaultValue: 0,
        unitType: "length"
      },
      y: {
        defaultValue: 0,
        unitType: "length"
      },
      fence: {
        defaultValue: 0,
      },
      // View options.
      color: {
        defaultValue: "transparent"
      },
      lineColor: {
        defaultValue: "black"
      },
      lineDashes: {
        defaultValue: "none"
      },
      lineWeight: {
        defaultValue: 1
      },
      layer: {
        defaultValue: 1
      },
      layerPosition: {
        defaultValue: 1
      },
      visible: {
        defaultValue: 1
      }
    },

    line: {
      // Required properties:
      x1: {
        defaultValue: 0,
        required: true,
        unitType: "length"
      },
      y1: {
        defaultValue: 0,
        required: true,
        unitType: "length"
      },
      x2: {
        defaultValue: 0,
        required: true,
        unitType: "length"
      },
      y2: {
        defaultValue: 0,
        required: true,
        unitType: "length"
      },
      // Optional properties:
      beginStyle: {
        defaultValue: "none",
      },
      endStyle: {
        defaultValue: "none",
      },
      fence: {
        defaultValue: 0,
      },
      // View options.
      lineColor: {
        defaultValue: "black"
      },
      lineDashes: {
        defaultValue: "none"
      },
      lineWeight: {
        defaultValue: 1
      },
      layer: {
        defaultValue: 1
      },
      layerPosition: {
        defaultValue: 1
      },
      visible: {
        defaultValue: 1
      }
    },

    radialBond: {
      atom1: {
        defaultValue: 0
      },
      atom2: {
        defaultValue: 0
      },
      length: {
        unitType: "length",
        required: true
      },
      strength: {
        unitType: "stiffness",
        required: true
      },
      type: {
        defaultValue: 101
      }
    },

    angularBond: {
      atom1: {
        defaultValue: 0
      },
      atom2: {
        defaultValue: 0
      },
      atom3: {
        defaultValue: 0
      },
      strength: {
        unitType: "rotationalStiffness",
        required: true
      },
      angle: {
        unitType: "angle",
        required: true
      }
    },

    restraint: {
      atomIndex: {
        required: true
      },
      k: {
        defaultValue: 2000,
        unitType: "stiffness"
      },
      x0: {
        defaultValue: 0,
        unitType: "length"
      },
      y0: {
        defaultValue: 0,
        unitType: "length"
      }
    },

    electricField: {
      intensity: {
        defaultValue: 0.004
      },
      orientation: {
        defaultValue: "E"
      },
      shapeIdx: {
        // Optional, electric field boundaries can be limited to a shape. When 'null' is used,
        // the electric field will be applied to the whole model area.
        defaultValue: null
      }
    },

    textBox: {
      text: {
        defaultValue: ""
      },
      x: {
        defaultValue: 0,
        unitType: "length"
      },
      y: {
        defaultValue: 0,
        unitType: "length"
      },
      anchor: {
        defaultValue: "lower-left"
      },
      layer: {
        defaultValue: 1
      },
      width: {},
      height: {},
      frame: {},
      color: {},
      calloutPoint: {},
      backgroundColor: {
        defaultValue: "white"
      },
      strokeWidthEms: {
        defaultValue: 0.03
      },
      strokeOpacity: {
        defaultValue: 1.0
      },
      strokeColor: {
        defaultValue: "#000000"
      },
      rotate: {
        defaultValue: 0
      },
      fontSize: {
        defaultValue: 0.12 // defined in nm!
      },
      hostType: {},
      hostIndex: {},
      textAlign: {}
    },

    chemicalReactions: {
      updateInterval: {
        defaultValue: 10
      },
      createAngularBonds: {
        // When this option is set to true, the algorithm will add angular bonds between triplet
        // of atoms. Angle calculation is based on the energy minimization and valence electrons
        // count.
        defaultValue: true
      },
      noLoops: {
        // If this option is enabled, the algorithm will ensure that no molecule will form a loop.
        // Note that it can have impact on performance and in many cases won't be possible due to
        // valence electrons configuration anyway.
        defaultValue: false
      },
      valenceElectrons: {
        defaultValue: [1, 1, 7, 7]
      },
      bondEnergy: {
        defaultValue: {
          // This configuration means that default bond chemical energy is 6eV, however single bonds
          // between the same elements (like bond between 1 and 1) have a bit smaller chemical
          // energy equal to 4eV. You can freely modify this configuration. Note that you should
          // define "default" key if you don't specify all possible configurations.
          // Single bond is defined by "-" symbol, e.g. 0-1 is a single bond between element 0 and 1.
          // Double bond is defined by "=" symbol, e.g. 1=2 is a double bond between element 1 and 2.
          // Triple bond is defined by "#" symbol, e.g. 2#3 is a triple bond between element 2 and 3.
          "default": 6,
          "0-0": 4,
          "1-1": 4,
          "2-2": 4,
          "3-3": 4
        }
      },
      activationEnergy: {
        defaultValue: {
          // This configuration means that default activation energy is equal to 0.2eV.
          // If you need custom parameters for various combinations, you can add e.g.:
          // "1+2-2": 0.5,
          // "2+1-1": 5
          // what means that when element 1 collides with two bonded elements 2, activation
          // energy that causes bonds exchange is 0.5 eV. Similarly, when element 2 collides with
          // two bonded elements 1, activation energy that causes bonds exchange is 5 eV.
          // Note that format is important! Single element is first, then "+" sign, then pair
          // description.
          "default": 0.2

        }
      },
      bondProbability: {
        defaultValue: {
          // This configuration means that when two colliding atoms have 3 unpaired electrons
          // (e.g. their valence electron count is equal to 5), there is 80% chances that
          // single bond will be formed between them, 15% that double and 5% that triple.
          // If you need custom probability for a specific elements configuration, you can add e.g:
          // "1-2": [0.6, 0.3, 0.1]
          // what has analogical meaning, but these values are limited only to elements 1 and 2.
          "default": [0.8, 0.15, 0.05]
        },
        validate: function (value) {
          Object.keys(value).forEach(function (key) {
            var p = value[key];
            if (Math.abs(p[0] + p[1] + p[2] - 1) > 1e-3) {
              throw new Error("Bond type probability values should sum to one.");
            }
          });
          return value;
        }
      }
    },

    image: {
      imageUri: {
        required: true
      },
      imageX: {
        defaultValue: 0,
        required: true
      },
      imageY: {
        defaultValue: 0,
        required: true
      },
      imageHostType: {
        defaultValue: ""
      },
      imageHostIndex: {
        defaultValue: 0
      },
      imageLayer: {
        defaultValue: 1
      },
      imageLayerPosition: {
        defaultValue: 1
      },
      visible: {
        defaultValue: true
      },
      rotation: {
        defaultValue: 0
      },
      scale: {
        defaultValue: 1,
      },
      opacity: {
        defaultValue: 1
      }
    },

    quantumDynamics: {
      elementEnergyLevels: {
        defaultValue: []
      },
      photons: {
        defaultValue: {}
      },
      radiationlessEmissionProbability: {
        defaultValue: 1
      },
      lightSource: {
        defaultValue: {
          on: false,
          monochromatic: true,
          frequency: 1,
          radiationPeriod: 1000,
          numberOfBeams: 10,
          angleOfIncidence: 0
        }
      }
    },

    photon: {
      x: {
        serialize: true
      },
      y: {
        serialize: true
      },
      vx: {
        defaultValue: 0,
        serialize: true
      },
      vy: {
        defaultValue: 0,
        serialize: true
      },
      angularFrequency: {
        serialize: true
      }
    }
  };
});

/*global define */

/**
  This plugin adds quantum dynamics functionality to the MD2D engine.

  Datatable changes`
    atoms:
      excitation: an int representing the current level of excitation of an atom, from
        floor (0) to an arbitrary level. In this model each atom is assumed to have one
        single electron that can be excited to any of a finite number of levels. The
        actual energy of each level is defined by the atom's element

  New serialized properties:

    elementEnergyLevels: A 2-dimensional array defining energy levels for each element

*/


define('models/md2d/models/engine/plugins/quantum-dynamics',['require','common/models/engines/clone-restore-wrapper','common/dispatch-support','../constants/index','../utils','arrays','common/array-types','models/md2d/models/metadata','common/validator'],function(require) {

  // static variables
  var CloneRestoreWrapper = require('common/models/engines/clone-restore-wrapper'),
      DispatchSupport     = require('common/dispatch-support'),
      constants           = require('../constants/index'),
      utils               = require('../utils'),
      arrays              = require('arrays'),
      arrayTypes          = require('common/array-types'),
      metadata            = require('models/md2d/models/metadata'),
      validator           = require('common/validator'),

      // in reality, 6.626E-34 m^2kg/s. Classic MW uses 0.2 in its units (eV * fs)
      PLANCK_CONSTANT = constants.convert(0.2, { from: constants.unit.EV, to: constants.unit.MW_ENERGY_UNIT }),

      // MW uses a "tolerance band" to decide if a photon's energy matches an energy level gap.
      // Reference: https://github.com/concord-consortium/mw/blob/d3f621ba87825888737257a6cb9ac9e4e4f63f77/src/org/concord/mw2d/models/PhotonicExcitor.java#L28
      ENERGY_GAP_TOLERANCE = constants.convert(0.05, { from: constants.unit.EV, to: constants.unit.MW_ENERGY_UNIT }),

      // Speed of light.
      // in reality, about 300 nm/fs! Classic uses 0.2 in its units (0.1Å/fs), which is 0.002 nm/fs:
      C = 0.002,
      TWO_PI = 2 * Math.PI,

      // expected value of lifetime of excited energy state, in fs
      LIFETIME = 200,
      EMISSION_PROBABILITY_PER_FS = 1/LIFETIME,

      INFRARED = 2.5,
      ULTRAVIOLET = 14.5,

      // dispatch events from handlePhotonAtomCollision
      PHOTON_ABSORBED = 1,
      PHOTON_EMITTED = 2;

  function QuantumDynamics(engine, _properties) {

    var properties           = validator.validateCompleteness(metadata.quantumDynamics, _properties),

        api,
        dispatch             = new DispatchSupport("photonAbsorbed"),

        elementEnergyLevels  = properties.elementEnergyLevels,
        pRadiationless       = properties.radiationlessEmissionProbability,
        pStimulatedEmission  = 0,

        lightSource          = properties.lightSource,

        dimensions           = engine.getDimensions(),

        excitationTime       = [],

        modelTime,

        atoms,
        elements,
        photons,

        nextPhotonId = 0,

        getRandomFrequency = function() {
          return INFRARED + ((ULTRAVIOLET - INFRARED) * Math.random());
        },

        updateAtomsTable = function() {
          var length = atoms.x.length;

          atoms.excitation = arrays.create(length, 0, arrayTypes.int8Type);
        },

        createPhotonsTable = function(serializedPhotons) {
          var length = 0;

          if (serializedPhotons.x) {
            length = Math.ceil(serializedPhotons.x.length / 10) * 10;
          }

          photons =  {
            id    : arrays.create(length, 0, arrayTypes.uint16Type),
            x     : arrays.create(length, 0, arrayTypes.floatType),
            y     : arrays.create(length, 0, arrayTypes.floatType),
            vx    : arrays.create(length, 0, arrayTypes.floatType),
            vy    : arrays.create(length, 0, arrayTypes.floatType),
            angularFrequency : arrays.create(length, 0, arrayTypes.floatType)
          };
        },

        currentlyOperatedPairs = [],  // all pairs being currently operated on

        atom1Idx, atom2Idx,           // current pair of atoms during thermal excitation

        u1, u2,                       // temporary velocity-calculation variables
        w1, w2,
        dxFraction, dyFraction,

        numPhotons = 0,

        copyPhotonData = function(serializedPhotons) {
          if (!serializedPhotons || !serializedPhotons.x) {
            return;
          }
          ['x', 'y', 'vx', 'vy', 'angularFrequency'].forEach(function(key) {
            arrays.copy(serializedPhotons[key], photons[key]);
          });

          for (var i = 0; i < photons.x.length; i++) {
            if (photons.vx[i] || photons.vy[i]) {
              numPhotons++;
              photons.id[i] = nextPhotonId++;
            }
          }
        },

        // Iterate over all photon-atom pairs, and allow them to interact if "touching".
        //
        // If a photons and atom are close enough, one of the following may happen:
        //   - the photon is absorbed, exciting the atom's electron to a higher energy level
        //   - (NOT YET IMPLEMENTED) the photon is absorbed, ionizing the atoms' electron
        //   - (NOT YET IMPLEMENTED) the photon triggers stimulated emission of a second photon
        //   - (NOT YET IMPLEMENTED) the photon is scattered
        //   - or no interaction occurs (the photon and atom are unmodified)
        //
        handlePhotonAtomCollisions = function() {
          var numAtoms = engine.getNumberOfAtoms(),
              i, len,
              x, y,
              atomIndex,
              r, rsq,
              dx, dy,
              collisionResult;

          for (i = 0, len = photons.x.length; i < len; i++) {
            if (!photons.vx[i] && !photons.vy[i]) {
              continue;
            }

            x = photons.x[i];
            y = photons.y[i];

            // TODO. Consider using the cell list to narrow down the list of atoms to those in the
            // same cell as the photon.
            for (atomIndex = 0; atomIndex < numAtoms; atomIndex++) {
              dx = atoms.x[atomIndex] - x;
              dy = atoms.y[atomIndex] - y;
              r = atoms.radius[atomIndex];
              // TODO. Cache rsq values?
              rsq = r*r;

              if (dx*dx + dy*dy < rsq) {
                collisionResult = handlePhotonAtomCollision(i, atomIndex);
                if (collisionResult === PHOTON_ABSORBED) {
                  // Break from iteration over atoms, and move on to the next photon.
                  break;
                }
                // TODO. Handle stimulated emission by remembering a list of photons to create
                // after the loop over photons completes.
              }
            }
          }
        },

        handlePhotonAtomCollision = function(photonIndex, atomIndex) {
          if (Math.random() < pStimulatedEmission) {
            // TODO. Stimulated emission.
            return PHOTON_EMITTED;
          } else {
            var preciseEnergy = tryToAbsorbPhoton(photonIndex, atomIndex);
            if (preciseEnergy !== false && preciseEnergy > 0) {
              removePhoton(photonIndex);
              // For some reason Classic MW doesn't show initial frequency of the photon on the spectrometer.
              // It's updated first using "preciseEnergy". Do the same to be consistent.
              var newFreq = preciseEnergy / PLANCK_CONSTANT;
              dispatch.photonAbsorbed(newFreq);
              return PHOTON_ABSORBED;
            }
          }
          // TODO. Scatter photon (or not) depending on the model's "scatter probability".
        },

        // If the photon can be absorbed by exciting the atom's electron to a higher energy level,
        // then remove the photon, excite the electron, and return the precise energy. Otherwise, return false.
        tryToAbsorbPhoton = function(photonIndex, atomIndex) {
          if (!elementEnergyLevels) return;

          var energyLevels     = elementEnergyLevels[atoms.element[atomIndex]],
              energyLevelIndex = atoms.excitation[atomIndex],
              electronEnergy   = energyLevels[energyLevelIndex],
              photonEnergy     = photons.angularFrequency[photonIndex] * PLANCK_CONSTANT,
              i,
              nLevels;

          for (i = energyLevelIndex + 1, nLevels = energyLevels.length; i < nLevels; i++) {
            if (Math.abs(energyLevels[i] - electronEnergy - photonEnergy) < ENERGY_GAP_TOLERANCE) {
              atoms.excitation[atomIndex] = i;
              excitationTime[atomIndex] = modelTime;
              return energyLevels[i] - electronEnergy;
            }
          }
          return false;
        },

        // If a pair of atoms are close enough, QD interactions may occur.
        //
        // This is called at the end of every integration loop.
        thermallyExciteAndDeexciteAtoms = function(neighborList) {
          var N     = engine.getNumberOfAtoms(),
              nlist = neighborList.getList(),
              currentlyClosePairs = [],
              a1, a2,
              i, len,
              el1, el2,
              energyLevels1, energyLevels2,
              xi, yi, xij, yij, ijsq,
              avrSigma, avrSigmaSq,
              atomWasExcited, atomWasDeexcited;

          if (!elementEnergyLevels) return;

          // get all proximal pairs of atoms, using neighborList
          for (a1 = 0; a1 < N; a1++) {

            xi = atoms.x[a1];
            yi = atoms.y[a1];

            for (i = neighborList.getStartIdxFor(a1), len = neighborList.getEndIdxFor(a1); i < len; i++) {
              a2 = nlist[i];

              el1 = atoms.element[a1];
              el2 = atoms.element[a2];
              energyLevels1 = elementEnergyLevels[el1];
              energyLevels2 = elementEnergyLevels[el2];

              // if neither atom is of an element with energy levels, skip
              if (!energyLevels1.length && !energyLevels2.length) {
                continue;
              }

              // if we aren't close (within the avrSigma of two atoms), skip
              xij = xi - atoms.x[a2];
              yij = yi - atoms.y[a2];
              ijsq = xij * xij + yij * yij;
              avrSigma = 0.55 * (elements.sigma[el1] + elements.sigma[el2]);
              avrSigmaSq = avrSigma * avrSigma;

              if (ijsq >= avrSigmaSq) {
                continue;
              }

              currentlyClosePairs[a1] = a2;   // add this pair to our temporary list of close pairs

              if (currentlyOperatedPairs[a1] === a2) {
                // we have already operated on this pair, and the atoms have not yet
                // left each other's neighborhoods, so we skip so as not to operate
                // on them twice in one collision
                continue;
              }

              // first try to see if we can excite atoms
              atomWasExcited = tryToThermallyExciteAtoms(a1, a2);

              // if we didn't excite, see if this pair wants to de-excite
              if (!atomWasExcited) {
                atomWasDeexcited = tryToThermallyDeexciteAtoms(a1, a2);
              }

              if (atomWasExcited || atomWasDeexcited) {
                // add pair to our operation list
                currentlyOperatedPairs[a1] = a2;
                currentlyOperatedPairs[a2] = a1;
              }
            }
          }

          // go through list of currently-operated pairs, and if any of them aren't in
          // our temporary list of close pairs, they have left each other so we can
          // strike them from the list
          for (a1 = 0, len = currentlyOperatedPairs.length; a1 < len; a1++) {
            a2 = currentlyOperatedPairs[a1];
            if (!isNaN(a2)) {
              if (!(currentlyClosePairs[a1] === a2 || currentlyClosePairs[a2] === a1)) {
                delete currentlyOperatedPairs[a1];
                delete currentlyOperatedPairs[a2];
              }
            }
          }
        },

        // If a pair of atoms are close enough, and their relative KE is greater than
        // the energy required to reach a new excitation level of a random member of
        // the pair, increase the excitation level of that atom and adjust the velocity
        // of the pair as required.
        tryToThermallyExciteAtoms = function(a1, a2) {
          var atomWasExcited,
              selection;

          atom1Idx = a1;
          atom2Idx = a2;

          // excite a random atom, or pick the excitable one if only one can be excited
          selection = Math.random() < 0.5 ? atom1Idx : atom2Idx;
          atomWasExcited = tryToExciteAtom(selection);
          if (!atomWasExcited) {
            // if we couldn't excite the first, excite the other one
            atomWasExcited = tryToExciteAtom(atom1Idx+atom2Idx-selection);
          }

          return atomWasExcited;
        },

        // Excites an atom to a new energy level if the relative KE of the pair atom1Idx
        // and atom2Idx is high enough, and updates the velocities of atoms as necessary
        tryToExciteAtom = function(i) {
          var energyLevels   =   elementEnergyLevels[atoms.element[i]],
              currentEnergyLevel,
              currentElectronEnergy,
              relativeKE,
              energyRequired, highest,
              nextEnergyLevel, energyAbsorbed,
              j, jj;

          if (!energyLevels) return;

          computeVelocityComponents();

          relativeKE = getRelativeKE();

          currentEnergyLevel = atoms.excitation[i];
          currentElectronEnergy = energyLevels[currentEnergyLevel];

          // get the highest energy level above the current that the relative KE can reach
          for (j = currentEnergyLevel+1, jj = energyLevels.length; j < jj; j++) {
            energyRequired = energyLevels[j] - currentElectronEnergy;
            if (relativeKE < energyRequired) {
              break;
            }
            highest = j;
          }
          if (!highest) {
            // there is no higher energy level we can reach
            return false;
          }

          // assuming that all the energy levels above have the same chance of
          // getting the excited electron, we randomly pick one.
          highest = highest - currentEnergyLevel;
          nextEnergyLevel = Math.ceil(Math.random() * highest) + currentEnergyLevel;

          atoms.excitation[i] = nextEnergyLevel;
          excitationTime[i] = modelTime;
          energyAbsorbed = energyLevels[nextEnergyLevel] - currentElectronEnergy;
          updateVelocities(energyAbsorbed);
          return true;
        },

        computeVelocityComponents = function() {
          var dx = atoms.x[atom2Idx] - atoms.x[atom1Idx],
              dy = atoms.y[atom2Idx] - atoms.y[atom1Idx],
              normalizationFactor = 1 / Math.sqrt(dx*dx + dy*dy);

          dxFraction = dx * normalizationFactor;
          dyFraction = dy * normalizationFactor;

          // Decompose v1 into components u1 (parallel to d) and w1 (orthogonal to d)
          u1 = atoms.vx[atom1Idx] * dxFraction + atoms.vy[atom1Idx] * dyFraction;
          w1 = atoms.vy[atom1Idx] * dxFraction - atoms.vx[atom1Idx] * dyFraction;

          // Decompose v2 similarly
          u2 = atoms.vx[atom2Idx] * dxFraction + atoms.vy[atom2Idx] * dyFraction;
          w2 = atoms.vy[atom2Idx] * dxFraction - atoms.vx[atom2Idx] * dyFraction;
        },

        getRelativeKE = function() {
          var du = u2 - u1,
              m1 = atoms.mass[atom1Idx],
              m2 = atoms.mass[atom2Idx];

          return 0.5 * du * du * m1 * m2 / (m1 + m2);
        },

        updateVelocities = function(energyDelta) {
          var m1 = atoms.mass[atom1Idx],
              m2 = atoms.mass[atom2Idx],
              j  = m1 * u1 * u1 + m2 * u2 * u2 - energyDelta,
              g  = m1 * u1 + m2 * u2,
              v1 = (g - Math.sqrt(m2 / m1 * (j * (m1 + m2) - g * g))) / (m1 + m2),
              v2 = (g + Math.sqrt(m1 / m2 * (j * (m1 + m2) - g * g))) / (m1 + m2);

          atoms.vx[atom1Idx] = v1 * dxFraction - w1 * dyFraction;
          atoms.vy[atom1Idx] = v1 * dyFraction + w1 * dxFraction;
          atoms.vx[atom2Idx] = v2 * dxFraction - w2 * dyFraction;
          atoms.vy[atom2Idx] = v2 * dyFraction + w2 * dxFraction;
        },

        // If one atom has an electron in a higher energy state (and we didn't just excite this
        // pair) the atom may deexcite during a collision. This will either release a photon or will
        // increase the relative KE of the atoms (radiationless transition), with the probabilities
        // of each depending on the model settings.
        tryToThermallyDeexciteAtoms = function(a1, a2) {
          var selection,
              excitation1 = atoms.excitation[a1],
              excitation2 = atoms.excitation[a2];

          atom1Idx = a1;
          atom2Idx = a2;

          if (!excitation1 && !excitation2) {
            return false;
          }

          // excite a random atom, or pick the excitable one if only one can be excited
          if (!excitation1) {
            if (!readyToThermallyDeexcite(atom2Idx)) return false;
            selection = atom2Idx;
          } else if (!excitation2) {
            if (!readyToThermallyDeexcite(atom1Idx)) return false;
            selection = atom1Idx;
          } else {
            selection = Math.random() < 0.5 ? atom1Idx : atom2Idx;
            if (!readyToThermallyDeexcite(selection)) {
              selection = atom1Idx + atom2Idx - selection;
              if (!readyToThermallyDeexcite(selection)) {
                return false;
              }
            }
          }
          deexciteAtom(selection);
          return true;
        },

        readyToThermallyDeexcite = function(i) {
          if (modelTime > excitationTime[i] + LIFETIME) {
            return true;
          }
        },

        deexciteAtom = function(i) {
          var energyLevels   = elementEnergyLevels[atoms.element[i]],
              currentLevel   = atoms.excitation[i],
              newLevel       = Math.floor(Math.random() * currentLevel),
              energyReleased = energyLevels[newLevel] - energyLevels[currentLevel];

          atoms.excitation[i] = newLevel;

          if (Math.random() < pRadiationless) {
            // new energy goes into increasing atom velocities after collision
            computeVelocityComponents();
            updateVelocities(energyReleased);
          } else {
            emitPhotonFromAtom(i, -energyReleased);
          }
        },

        findEmptyPhotonIndex = function() {
          var length = photons.x.length,
              i;

          if (numPhotons + 1 > length) {
            utils.extendArrays(photons, length+10);
            return length;
          }

          for (i = 0; i < length; i++) {
            if (!photons.vx[i] && !photons.vy[i]) {
              return i;
            }
          }
        },

        removePhoton = function(i) {
          numPhotons--;
          photons.x[i] = photons.y[i] = photons.vx[i] = photons.vy[i] = photons.angularFrequency[i] = 0;
        },

        emitPhoton = function(x, y, angle, energy) {
          var cosA  = Math.cos(angle),
              sinA  = Math.sin(angle),
              vx          = C * cosA,
              vy          = C * sinA,
              angularFreq = energy / PLANCK_CONSTANT,
              photonIndex = findEmptyPhotonIndex();

          numPhotons++;
          photons.id[photonIndex] = nextPhotonId++;
          photons.x[photonIndex]  = x;
          photons.y[photonIndex]  = y;
          photons.vx[photonIndex] = vx;
          photons.vy[photonIndex] = vy;
          photons.angularFrequency[photonIndex] = angularFreq;
        },

        emitPhotonFromAtom = function(atomIndex, energy) {
          var angle = Math.random() * TWO_PI - Math.PI,
              cosA  = Math.cos(angle),
              sinA  = Math.sin(angle),
              sigma = elements.sigma[atoms.element[atomIndex]],

              // set photon location just outside atom's sigma
              x = atoms.x[atomIndex] + (sigma * 0.51 * cosA),
              y = atoms.y[atomIndex] + (sigma * 0.51 * sinA);

          emitPhoton(x, y, angle, energy);
        },

        movePhotons = function(dt) {
          var i, ii,
              x, y;

          for (i = 0, ii = photons.x.length; i < ii; i++) {
            if (!photons.vx[i] && !photons.vy[i]) continue;

            x = photons.x[i] += photons.vx[i] * dt;
            y = photons.y[i] += photons.vy[i] * dt;

            if (x < dimensions[0] || x > dimensions[2] || y < dimensions[1] || y > dimensions[3]) {
              removePhoton(i);
            }
          }
        },

        spontaneouslyEmitPhotons = function(dt) {
          if (!elementEnergyLevels) { return; }

          for (var i = 0, N = engine.getNumberOfAtoms(); i < N; i++) {
            tryToSpontaneouslyEmitPhoton(i, dt);
          }
        },

        tryToSpontaneouslyEmitPhoton = function(atomIndex, dt) {

          if (atoms.excitation[atomIndex] === 0) { return; }

          // The probability of an emission in the current timestep is the probability that an
          // exponential random variable T with expected value LIFETIME has value t less than dt.
          // For dt < ~0.1 * LIFETIME, this probability is approximately equal to dt/LIFETIME.

          if (Math.random() > dt * EMISSION_PROBABILITY_PER_FS) { return; }

          // Randomly select an energy level. Reference:
          // https://github.com/concord-consortium/mw/blob/6e2f2d4630323b8e993fcfb531a3e7cb06644fef/src/org/concord/mw2d/models/SpontaneousEmission.java#L48-L70

          var r1 = Math.random(),
              r2 = Math.random(),
              energyLevels,
              excessEnergy,
              i,
              m = atoms.excitation[atomIndex],
              mInverse = 1/m;

          for (i = 0; i < m; i++) {
            if (i*mInverse <= r1 && r1 < (i+1)*mInverse && pRadiationless < r2) {
              energyLevels = elementEnergyLevels[atoms.element[atomIndex]];
              excessEnergy = energyLevels[m] - energyLevels[i];
              atoms.excitation[atomIndex] = i;
              emitPhotonFromAtom(atomIndex, excessEnergy);
              return;
            }
          }
        },

        normalizeAngle = function(t) {
          t = t % TWO_PI;
          if (t < 0 || t > TWO_PI)
            return Math.abs((TWO_PI) - Math.abs(t));
          return t;
        },

        // Temporary implementation with hard-wired parameters.
        emitLightSourcePhotons = function() {
          var x = dimensions[0],
              y = dimensions[1],
              w = dimensions[2] - x,
              h = dimensions[3] - y,

              angle  = normalizeAngle(lightSource.angleOfIncidence),
              nBeams = lightSource.numberOfBeams,
              spacing,
              s, c, length, dx, dy, m, n, i,

              getEnergy = function () {
                return (lightSource.monochromatic ? lightSource.frequency : getRandomFrequency()) * PLANCK_CONSTANT;
              };

          if (angle == 0) {
            spacing = h / (nBeams + 1);
            for (i = 1; i <= nBeams; i++) {
              emitPhoton(x, y + spacing * i, angle, getEnergy());
            }
          } else if (angle.toFixed(4) == (Math.PI/2).toFixed(4)) {
            spacing = w / (nBeams + 1);
            for (i = 1; i <= nBeams; i++) {
              emitPhoton(x + spacing * i, y, angle, getEnergy());
            }
          } else if (angle.toFixed(4) == (Math.PI).toFixed(4)) {
            spacing = h / (nBeams + 1);
            for (i = 1; i <= nBeams; i++) {
              emitPhoton(x + w, y + spacing * i, angle, getEnergy());
            }
          } else if (angle.toFixed(4) == (Math.PI*3/2).toFixed(4)) {
            spacing = w / (nBeams + 1);
            for (i = 1; i <= nBeams; i++) {
              emitPhoton(x + spacing * i, y + h, angle, getEnergy());
            }
          } else {
            // Lifted from AtomicModel.shootPhotons()
            // https://github.com/concord-consortium/mw/blob/d3f621ba87825888737257a6cb9ac9e4e4f63f77/src/org/concord/mw2d/models/AtomicModel.java#L2534
            s = Math.abs(Math.sin(angle));
            c = Math.abs(Math.cos(angle));
            length = s * h < c * w ? h / c : w / s;
            spacing = length / nBeams;
            dx = spacing / s;
            dy = spacing / c;
            m = Math.floor(w / dx);
            n = Math.floor(h / dy);

            // Lifted from AtomicModel.shootAtAngle()
            // https://github.com/concord-consortium/mw/blob/d3f621ba87825888737257a6cb9ac9e4e4f63f77/src/org/concord/mw2d/models/AtomicModel.java#L2471
            if (angle >= 0 && angle < 0.5 * Math.PI) {
              for (i = 1; i <= m; i++)
                emitPhoton(x + dx * i, y, angle, getEnergy());
              for (i = 0; i <= n; i++)
                emitPhoton(x, y + h - dy * i, angle, getEnergy());
            } else if (angle >= Math.PI*3/2) {
              for (i = 1; i <= m; i++)
                emitPhoton(x + dx * i, y + h, angle, getEnergy());
              for (i = 0; i <= n; i++)
                emitPhoton(x, y + dy * i, angle, getEnergy());
            } else if (angle < Math.PI && angle >= 0.5 * Math.PI) {
              for (i = 0; i <= m; i++)
                emitPhoton(x + w - dx * i, y, angle, getEnergy());
              for (i = 1; i <= n; i++)
                emitPhoton(x + w, y + h - dy * i, angle, getEnergy());
            } else if (angle >= Math.PI && angle < Math.PI*3/2) {
              for (i = 0; i <= m; i++)
                emitPhoton(x + w - dx * i, y + h, angle, getEnergy());
              for (i = 1; i <= n; i++)
                emitPhoton(x + w, y + dy * i, angle, getEnergy());
            }
          }
        };


    // Public API.
    api = {
      initialize: function(dataTables) {
        atoms     = dataTables.atoms;
        elements  = dataTables.elements;
        updateAtomsTable();
        createPhotonsTable(properties.photons);
        copyPhotonData(properties.photons);
      },

      performActionWithinIntegrationLoop: function(neighborList, dt, time) {
        modelTime = time;
        movePhotons(dt);
        handlePhotonAtomCollisions();
        thermallyExciteAndDeexciteAtoms(neighborList);
        spontaneouslyEmitPhotons(dt);
        // Temporary hard-wired light source, for demo purposes

        if (lightSource.on && time % lightSource.radiationPeriod < dt) {
          emitLightSourcePhotons();
        }
      },

      turnOnLightSource: function() {
        lightSource.on = true;
      },

      turnOffLightSource: function() {
        lightSource.on = false;
      },

      setLightSourceAngle: function(angle) {
        lightSource.angleOfIncidence = angle;
      },

      setLightSourceFrequency: function(freq) {
        lightSource.frequency = freq;
      },

      setLightSourcePeriod: function(period) {
        lightSource.radiationPeriod = period;
      },

      setLightSourceNumber: function(number) {
        lightSource.numberOfBeams = number;
      },

      getPhotons: function() {
        return photons;
      },

      getNumPhotons: function() {
        return numPhotons;
      },

      // TODO/FIXME: This is a modeler-level method; it's here until the plugin mechanism is
      // extended to allow plugins to define both engine-level and modeler-level parts.
      // Additionally, this can be split into updateViewPhotons which can happen in the
      // modeler's readModelState method, and a simple getViewPhotons accessor used by the view.
      getViewPhotons: (function() {
        var viewPhotons = [],
            viewPhotonsByIndex = [];

        function makeViewPhoton(photons, i) {
          var vx = photons.vx[i],
              vy = photons.vy[i],
              // For convenience, this is in the form required by SVG transform
              angle = -180 * Math.atan2(vy, vx) / Math.PI;

          return {
            id: photons.id[i],
            x:  photons.x[i],
            y:  photons.y[i],
            vx: vx,
            vy: vy,
            angle: angle,
            angularFrequency: photons.angularFrequency[i]
          };
        }

        return function() {
          // avoid using the closure variable 'photons' as this method will be relocated to
          // modeler, and will then have to access photons table via some kind of accessor method
          var photons = this.getPhotons(),
              n = 0,
              i,
              len,
              viewPhoton;

          viewPhotons.length = this.getNumPhotons();
          viewPhotonsByIndex.length = photons.x.length;

          for (i = 0, len = photons.x.length; i < len; i++) {
            if (photons.vx[i] || photons.vy[i]) {
              viewPhoton = viewPhotonsByIndex[i];

              // If we have a viewPhoton for slot i in the photons array, update it instead of
              // allocating a new viewPhoton object; that will tell the view code to update the
              // position of the squiggle instead of generating a new one. Note that we also need to
              // make sure that that slot in the photons array still represents the same photon it
              // did last time we were called.
              if (viewPhoton && viewPhoton.id === photons.id[i]) {
                viewPhoton.x = photons.x[i];
                viewPhoton.y = photons.y[i];
              } else {
                viewPhoton = makeViewPhoton(photons, i);
                viewPhotonsByIndex[i] = viewPhoton;
              }
              viewPhotons[n++] = viewPhoton;
            } else {
              // Release references to the viewPhoton object after we're done with it.
              viewPhotonsByIndex[i] = null;
            }
          }

          return viewPhotons;
        };
      }()),

      getElementEnergyLevels: function() {
        return elementEnergyLevels;
      },

      getRadiationlessEmissionProbability: function() {
        return pRadiationless;
      },

      getLightSource: function() {
        if (!lightSource) return undefined;
        return lightSource;
      },

      getState: function() {
        return [
          new CloneRestoreWrapper(photons, { padArraysWithZeroes: true }),
          {
            clone: function() {
              return {
                numPhotons: numPhotons
              };
            },
            restore: function(state) {
              numPhotons = state.numPhotons;
            }
          }
        ];
      }
    };

    dispatch.mixInto(api);

    return api;
  }

  // Export constants.
  QuantumDynamics.INFRARED = INFRARED;
  QuantumDynamics.ULTRAVIOLET = ULTRAVIOLET;

  return QuantumDynamics;

});

/*global define, $ */

define('common/controllers/spectrometer-controller',['common/inherit','common/controllers/interactive-component','models/md2d/models/engine/plugins/quantum-dynamics'],function () {
  var inherit              = require('common/inherit'),
      InteractiveComponent = require('common/controllers/interactive-component'),

      QuantumDynamics      = require('models/md2d/models/engine/plugins/quantum-dynamics'),
      INFRARED             = QuantumDynamics.INFRARED,
      ULTRAVIOLET          = QuantumDynamics.ULTRAVIOLET,

      // Performance optimization - photon mark is not rendered if there is already another
      // photon mark at the same position. Positions are rounded to the PHOTON_MARKS_PRECISION.
      PHOTON_MARKS_PRECISION = 2,

      // Visible light spectrum image (297px x 1px). Covers area from INFRARED to ULTRAVIOLET.
      // It's based on the Classic MW spectrometer.
      VISIBLE_LIGHT_IMG_DATA = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASkAAAABCAYAAACCEVhtAAAAmUlEQVQ4T8WUw' +
                               'Q6DIBBEn1Wr+P/fCnjCbGuTLe4GbphMnH0ZhkiIUymlMOo5T0jpXzk/mWQ097zusnpihBXYgP1+i38rL/NP' +
                               'HteZVl72mYBXp3qzjdwJpErZYJLR3PO6y+qJnzskhxEqyQHUTGbNPa/XWT0HDPjQhe81EmnfM3vrWnzeYAm' +
                               'VdoNJRvPbrwFmI+/yY9RP4bnvBfEAd/7c+ytNAAAAAElFTkSuQmCC';

  function SpectrometerController(component, interactivesController) {
    // Call super constructor.
    InteractiveComponent.call(this, 'spectrometer', component, interactivesController);

    this.$element
      .addClass('interactive-spectrometer')
      .css('border', this.component.border);

    this.lowerBound = this.component.lowerBound;
    this.upperBound = this.component.upperBound;

    // Helper objects that prevents spectrometer from drawing multiple photon marks at the same position.
    this._existingPhotonMarks = {};
    
    this.renderBackground();
    this.renderTickMarks();
    this.$photonMarksContainer = $('<div>').appendTo(this.$element);
  }

  inherit(SpectrometerController, InteractiveComponent);

  SpectrometerController.prototype.modelLoadedCallback = function () {
    SpectrometerController.superClass._modelLoadedCallback.call(this);
    this._model.onPhotonAbsorbed(this.onPhotonAbsorbed.bind(this));
    if (this.component.clearOnModelLoad) {
      this.$photonMarksContainer.empty();
    }
  };

  SpectrometerController.prototype.renderBackground = function () {
    var position = (INFRARED - this.lowerBound) / (this.upperBound - this.lowerBound) * 100;
    var width = (ULTRAVIOLET - INFRARED) / (this.upperBound - this.lowerBound) * 100;
    $('<img>')
      .addClass('spectrometer-bg')
      .attr('src', VISIBLE_LIGHT_IMG_DATA)
      .css('left', position + '%')
      .css('width', width + '%')
      .appendTo(this.$element);
  };

  SpectrometerController.prototype.renderTickMarks = function () {
    if (!this.component.ticks || this.component.ticks < 2) return;

    var $tickMarksContainer = $('<div>').appendTo(this.$element);
    var spacing = 100 / this.component.ticks;
    for (var i = 1; i < this.component.ticks; i++) {
      $('<div class="tick-mark">').css('left', (i * spacing) + '%').appendTo($tickMarksContainer);
    }
  };

  SpectrometerController.prototype.onPhotonAbsorbed = function (frequency) {
    if (frequency > this.upperBound || frequency < this.lowerBound) return;

    var position = (frequency - this.lowerBound) / (this.upperBound - this.lowerBound) * 100;
    // Check existing photon marks, so we don't render hundreds of lines in the same place.
    if (!this.photonMarkExists(position)) {
      this.addPhotonMark(position);
    }
  };

  SpectrometerController.prototype.photonMarkExists = function (position) {
    return !!this._existingPhotonMarks[position.toFixed(PHOTON_MARKS_PRECISION)];
  };

  SpectrometerController.prototype.addPhotonMark = function (position) {
    $('<div class="photon-mark">').css('left', position + '%').appendTo(this.$photonMarksContainer);
    this._existingPhotonMarks[position.toFixed(PHOTON_MARKS_PRECISION)] = true;
  };

  return SpectrometerController;
});

define('common/layout/detect-font-change',[],function() {
  // Size of the font we test, it doesn't really matter.
  var FONT_SIZE = 15; // px

  // how long to poll
  var DEFAULTS = {
    weight: 'normal',
    timeout: 3000,
    interval: 250,
    onchange: null
  };

  var fontLoaded = {};
  var loading = {};

  function getBitmap(font) {
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    var dim = FONT_SIZE * 1.5;

    canvas.width = dim;
    canvas.height = dim;

    ctx.font = font;
    ctx.fillText('A', 0, dim);
    return canvas.toDataURL();
  }

  function stopChecking(font) {
    window.clearInterval(loading[font].pollingInterval);
    delete loading[font];
  }

  function fontChecker(font, startTime, timeout) {
    loading[font].bitmap = getBitmap(font);

    return function() {
      if (getBitmap(font) !== loading[font].bitmap) {
        loading[font].changeCallbacks.forEach(function (cb) {
          cb();
        });
        fontLoaded[font] = true;
        stopChecking(font);
        return;
      }

      if (Date.now() - startTime > timeout) {
        stopChecking(font);
      }
    };
  }

  /**
    Detects changes to how a given font renders in a Canvas context, using the assumption that the
    first such change indicates that the font has loaded and should no longer be checked.
    If you provide multiple fonts (e.g. Lato, 'Open Sans', Arial), onchange handler can be called
    multiple times.

    options:
      font:   a font family name in the format used by the CSS font-family property.
              See https://developer.mozilla.org/en-US/docs/Web/CSS/font-family

      weight: a font weight in the format used by the CSS font-weight property.
              See https://developer.mozilla.org/en/docs/Web/CSS/font-weight

      interval: Length in milliseconds of the interval to use for checking a font for changes.

      timeout: How many milliseconds to wait before indicating an error

      onchange: A function to be called when we detect a change to the way the font renders. This is
                not a promise-style callback that indicates the font is loaded; it is only called
                when we detect a difference in the bitmap created when rendering canvas fillText
                using this font. It will be called if this method returns true and the font-checking
                does not timeout. It is always called asynchronously (i.e, in a later event loop.)

    returns:
      true, if the font has not yet loaded
      false, if the font is already loaded

    semantics:

      for a given font specifier
        if it has been loaded already
          return false
        if it has not loaded
          and we are not already polling for changes to that font:
            add 'onchange' to the list of onchange listeners
            return true
          else:
            begin polling every interval milliseconds, for at most 'timeout' milliseconds
              if it changes during that interval
               call onchange listeners
               cancel polling interval
            return true
  */
  return function detectFontChange(_options) {
    var options = {};

    // option processing
    Object.keys(_options).concat(Object.keys(DEFAULTS)).forEach(function(key) {
      options[key] = _options[key] != null ? _options[key] : DEFAULTS[key];
    });

    var multipleFonts = options.font.split(',');
    if (multipleFonts.length > 1) {
      var result = false;
      multipleFonts.forEach(function(fontName) {
        options.font = fontName.trim();
        if (detectFontChange(options)) {
          // Return true if at least one font is not already loaded.
          result = true;
        }
      });
      return result;
    }

    // Construct compact form that is expected by canvas.
    var font = options.weight + ' ' + FONT_SIZE + 'px ' + options.font;

    if (fontLoaded[font]) {
      // Font already loaded.
      return false;
    }

    if ( ! loading[font] ) {
      loading[font] = {
        bitmap: null,
        pollingInterval: null,
        changeCallbacks: []
      };
    }

    if (options.onchange) {
      loading[font].changeCallbacks.push(options.onchange);
    }

    if ( ! loading[font].pollingInterval ) {
      loading[font].pollingInterval = window.setInterval(
        fontChecker(font, Date.now(), options.timeout),
        options.interval
      );
    }

    return true;
  };

});

define('common/views/numeric-output-view',['common/layout/detect-font-change'],function () {

  var detectFontChange = require('common/layout/detect-font-change');
  var OVERSAMPLE = 2;

  return function NumericOutputView(opts) {

    var id    = opts.id;
    var label = opts.label;
    var units = opts.unit;

    var $numericOutput;
    var $label;
    var $output;
    var $number;
    var $units;

    var lastValue;
    var minContentWidth;

    var $canvas;
    var textWidth = 0;
    var textY;
    var ctx;
    var canvasElementWidth, canvasElementHeight;
    var canvasInternalWidth, canvasInternalHeight;

    var api;

    function resizeCanvas(width) {
      var oversampledFontSize;

      canvasElementWidth = width;
      canvasElementHeight = $number.height();

      $canvas.width(canvasElementWidth);
      $canvas.height(canvasElementHeight);

      // oversample for HiDPI devices (set its internal width, height to 2x those of canvas element)
      canvasInternalWidth = OVERSAMPLE * canvasElementWidth;
      canvasInternalHeight = OVERSAMPLE * canvasElementHeight;

      $canvas.attr('width', canvasInternalWidth);
      $canvas.attr('height', canvasInternalHeight);

      // resizing resets internal canvas properties!
      oversampledFontSize = OVERSAMPLE * parseInt($number.css('font-size'), 10);

      ctx.font = [
        $number.css('font-style'),
        $number.css('font-variant'),
        $number.css('font-weight'),
        oversampledFontSize + 'px/' + $number.css('line-height'),
        $number.css('font-family')
      ].join(' ');

      ctx.fillStyle = $number.css('color');
      ctx.textAlign = 'right';
      ctx.textBaseline = 'middle';

      textY = canvasInternalHeight / 2;
    }

    function repositionCanvas() {
      var position = $output.position();
      $canvas.css({
        left: position.left + parseInt($number.css('padding-left'), 10) + $number.width() - canvasElementWidth,
        top: position.top + parseInt($output.css('padding-top'), 10)
      });
    }

    api = {

      /**
        Update the canvas element to the current value, and conservatively update the width of the
        background <span>.

        Numeric outputs update every tick (usually), so we're using canvas for speed (Drawing text
        to Canvas is faster than drawing to an absolutely positioned html element, and is much
        faster than updating an element in page flow, which invalidates the whole page layout and
        costs several full ms in layout and paint time even on a fast desktop browser.)

        Note that we need $number to be in page flow (position: relative) in order for semantic
        layout to work.

        We must update the size of $number but we to avoid performance hits we do so conservatiely
        -- only do it if the width of the number being displayed is > 5px larger or smaller than the
        width of $number.
      */
      update: function(value) {
        if (value == null) {
          value = '';
        } else {
          value = '' + value;
        }
        lastValue = value;

        var positionChanged = false;

        textWidth = Math.round(ctx.measureText(value).width / OVERSAMPLE);

        if (canvasElementWidth < textWidth) {
          resizeCanvas(2 * textWidth);
          positionChanged = true;
        }

        // Avoid resizing the number <span> each tick.
        if (Math.abs($number.width() - textWidth) > 5 && textWidth > minContentWidth) {
          $number.width(textWidth);
          positionChanged = true;
        }

        if (positionChanged) {
          repositionCanvas();
        }

        ctx.clearRect(0, 0, canvasInternalWidth, canvasInternalHeight);
        ctx.fillText(value, canvasInternalWidth, textY);
      },

      updateLabel: function(value) {
        $label.html(value);
      },

      updateUnits: function(value) {
        $units.html(value);
      },

      hideUnits: function() {
        // avoid growing/shrinking the box unnecessarily
        $units.css('opacity', 0);
      },

      showUnits: function() {
        $units.css('opacity', 1);
      },

      render: function() {
        $canvas = $('<canvas>').css('position', 'absolute');
        ctx = $canvas[0].getContext('2d');

        $numericOutput = $('<div class="numeric-output">');
        $label  = $('<span class="label"></span>');
        $output = $('<span class="output"></span>');
        // $number has to have content just so that its height is reported correctly
        $number = $('<span class="value"> - </span>').css('opacity', 0);
        $units  = $('<span class="units"></span>');

        if (label) { $label.html(label); }
        if (units) { $units.html(units); }

        $numericOutput.attr('id', id)
          .append($label)
          .append($output
            .append($canvas)
            .append($number)
            .append($units)
          );

        return $numericOutput;
      },

      /**
        Call this whenever app resizes. Updates canvas position, size, and font-size.
        Must be called at least once after the view has been added to the DOM!
      */
      resize: function() {
        minContentWidth =
          parseInt($number.css('min-width'), 10) -
          parseInt($number.css('padding-left'), 10) -
          parseInt($number.css('padding-right'), 10);

        resizeCanvas(2 * textWidth);
        api.update(lastValue);
        repositionCanvas();

        detectFontChange({
          font: ctx.font,
          onchange: function() {
            api.resize();
          }
        });
      }
    };

    return api;
  };
});

/*global define, $ */

define('common/controllers/numeric-output-controller',['common/controllers/interactive-metadata','common/validator','common/controllers/help-icon-support','common/views/numeric-output-view'],function () {

  var metadata          = require('common/controllers/interactive-metadata'),
      validator         = require('common/validator'),
      helpIconSupport   = require('common/controllers/help-icon-support'),
      NumericOutputView = require('common/views/numeric-output-view');

  return function NumericOutputController(component, interactivesController) {
    var propertyName,
        label,
        units,
        displayValue,
        view,
        $element,
        propertyDescription,
        controller,
        model,
        scriptingAPI;

    function renderValue() {
      var value = model.properties[propertyName];

      if (displayValue) {
        value = displayValue(value);
      }
      view.update(value);
    }

    //
    // Initialization.
    //
    model = interactivesController.getModel();
    scriptingAPI = interactivesController.getScriptingAPI();

    // Validate component definition, use validated copy of the properties.
    component = validator.validateCompleteness(metadata.numericOutput, component);

    propertyName = component.property;
    units = component.units;
    label = component.label;
    displayValue = component.displayValue;

    view = new NumericOutputView({
      id: component.id,
      units: units,
      label: label
    });

    $element = view.render();

    // Each interactive component has to have class "component".
    $element.addClass("component");

    // Add class defining component orientation - "horizontal" or "vertical".
    $element.addClass(component.orientation);

    // Custom dimensions.
    $element.css({
      width: component.width,
      height: component.height
    });

    if (displayValue) {
      displayValue = scriptingAPI.makeFunctionInScriptContext('value', displayValue);
    }

    if (component.tooltip) {
      $element.attr("title", component.tooltip);
    }

    // Public API.
    controller = {
      // This callback should be trigger when model is loaded.
      modelLoadedCallback: function () {
        if (model) {
          model.removeObserver(propertyName, renderValue);
        }
        model = interactivesController.getModel();
        scriptingAPI = interactivesController.getScriptingAPI();
        if (propertyName) {
          propertyDescription = model.getPropertyDescription(propertyName);
          if (propertyDescription) {
            if (!label) { view.updateLabel(propertyDescription.getLabel()); }
            if (!units) { view.updateUnits(propertyDescription.getUnitAbbreviation()); }
          }
          renderValue();
          model.addObserver(propertyName, renderValue);
        }
      },

      // Returns view container. Label tag, as it contains checkbox anyway.
      getViewContainer: function () {
        return $element;
      },

      resize: function() {
        view.resize();
        renderValue();
      },

      // Returns serialized component definition.
      serialize: function () {
        // Return the initial component definition.
        // Numeric output component doesn't have any state, which can be changed.
        // It's value is defined by underlying model.
        return $.extend(true, {}, component);
      }
    };

    // Support optional help icon.
    helpIconSupport(controller, component, interactivesController.helpSystem);

    // Return Public API object.
    return controller;
  };
});

define('common/views/table-view',[],function() {

  return function TableView(opts, tableController) {
    var api,
        id          = opts.id,
        columns     = opts.columns,
        formatters  = opts.formatters,
        visibleRows = opts.visibleRows,
        blankRow    = opts.showBlankRow,
        title       = opts.title,
        width       = opts.width,
        height      = opts.height,
        tooltip     = opts.tooltip,
        klasses     = opts.klasses || [],
        headerWidths,
        $el,
        $tableWrapper,
        $table,
        $thead,
        $titlerow,
        $tbody,
        $bodyrows,
        tbodyPos,
        tbodyHeight,
        selected = [];

    function renderColumnTitles() {
      var i, $th;
      $titlerow.find('th').remove("th");
      for(i = 0; i < columns.length; i++) {
        $th = $('<th>');
        $th.text(columns[i].name);
        $th.click(columnSort);
        $titlerow.append($th);
      }
    }

    function setFormattedData($td, datum, colIdx) {
      if (typeof datum !== "undefined" && datum !== null) {
        if(typeof datum === "string") {
          $td.text(datum);
        } else if(typeof datum === "number") {
          $td.text(formatters[colIdx](datum));
        }
      } else {
        $td.html("&nbsp;");
      }
    }

    function formatNumericValues() {
      $tbody.find('td').html(function() {
        var $td = $(this);
        var datum = $td.data('datum');
        var colIdx = $td.data('index');
        setFormattedData($td, datum, colIdx);
      });
    }

    function columnSort(e) {
      var $title = $(this),
          ascending = "asc",
          descending = "desc",
          sortOrder;

      // Remove blank row and setup it again after storting to ensure that it's always at the end
      // of the table.
      removeBlankRow();
      sortOrder = ascending;
      if ($title.hasClass(ascending)) {
        $title.removeClass(ascending);
        $title.addClass(descending);
        sortOrder = descending;
      } else if ($title.hasClass(descending)) {
        $title.removeClass(descending);
        $title.addClass(ascending);
        sortOrder = ascending;
      } else {
        $title.addClass(descending);
        sortOrder = descending;
      }
      $title.siblings().removeClass("sorted");
      $bodyrows = $tbody.find("tr");
      $bodyrows.tsort('td:eq('+$title.index()+')',
        {
          sortFunction:function(a, b) {
            var anum = Math.abs(parseFloat(a.s)),
                bnum = Math.abs(parseFloat(b.s));
            if (sortOrder === ascending) {
              return anum === bnum ? 0 : (anum > bnum ? 1 : -1);
            } else {
              return anum === bnum ? 0 : (anum < bnum ? 1 : -1);
            }
          }
        }
      );
      $title.addClass("sorted");
      setupBlankRow();
      e.preventDefault();
    }

    function alignColumnWidths() {
      headerWidths = $thead.find('tr:first th').map(function() {
        return $(this).outerWidth();
      });

      $tbody.find('tr:first td').each(function(i) {
        $(this).outerWidth(headerWidths[i]);
      });
    }

    function getRowByIndex(rowIndex) {
      return $($tbody.find('tr')).filter(function() {
        return $(this).data("index") === rowIndex;
      });
    }

    function getRowIndexFromRow($tr) {
      return $tr.data('index');
    }

    function getRowVisiblity($tr) {
      var p = $tr.position();
      if (!p || !tbodyPos) return 0;
      if (p.top < tbodyPos.top) return -1;
      if (p.top > tbodyHeight) return 1;
      return 0;
    }

    function removeSelection(rowIndex) {
      var i = selected.indexOf(rowIndex);
      if (i !== -1) {
        selected.splice(i, 1);
        return getRowByIndex(rowIndex).removeClass('selected');
      }
    }

    function addSelection(rowIndex) {
      var $tr;
      if (selected.indexOf(rowIndex) === -1) {
        selected.push(rowIndex);
        $tr = getRowByIndex(rowIndex);
        $tr.addClass('selected');
        return $tr;
      }
    }

    function getRowOrderIndices() {
      var i, j, indices = [];
      for (i = 0; i < selected.length; i++) {
        j = selected[i];
        indices.push([j, getRowByIndex(j).index()]);
      }
      return indices.sort(function(a, b) {
        return a[1] - b[1];
      });
    }

    function fillSelection() {
      var i, rowOrderIndices, start, end;
      rowOrderIndices = getRowOrderIndices();
      start = rowOrderIndices[0];
      end = rowOrderIndices[rowOrderIndices.length-1];
      $bodyrows = $tbody.find("tr");
      for (i = start[1]; i <= end[1]; i++) {
        addSelection($($bodyrows[i]).data('index'));
      }
    }

    function clearSelection() {
      var i;
      for (i = 0; i < selected.length; i++) {
        getRowByIndex(selected[i]).removeClass('selected');
      }
      selected = [];
    }

    function appendSingleRow(rowData, index) {
      var i, datum, $tr, $td;
      $tr = $('<tr class="data">');
      $($tr).data('index', index);
      for(i = 0; i < columns.length; i++) {
        $td = $('<td>');
        $($td).data('index', i);
        datum = rowData[i];
        $td.data('datum', datum);
        setFormattedData($td, datum, i);
        $tr.append($td);
      }
      $tbody.append($tr);
      if ($tbody.find("tr").length < 2) {
        alignColumnWidths();
      }
    }

    function removeBlankRow() {
      $tbody.find(".blank").remove();
    }

    function setupBlankRow() {
      // Remove old blank row and add new one.
      removeBlankRow();
      if (!blankRow) return;
      var index = $tbody.find("tr").length;
      var i, $tr, $td;
      $tr = $('<tr class="data blank">');
      $($tr).data('index', index);
      for(i = 0; i < columns.length; i++) {
        $td = $('<td>');
        $($td).data('index', i);
        $td.html("&nbsp;");
        $tr.append($td);
      }
      $tbody.append($tr);
      if ($tbody.find("tr").length < 2) {
        alignColumnWidths();
      }
    }

    function scrollToBottom() {
      // Dummy, big number will cause that we will always scroll maximally to the bottom.
      $tbody.scrollTop(99999999);
    }

    function replaceDataRow(rowData, index) {
      var datum;

      if ($tbody.find('tr').length === 0) {
        api.appendDataRow(rowData, index);
        return;
      }

      var $tr = $($tbody.find('tr')).filter(function() {
            return $(this).data("index") === index;
          }),
          $dataElements = $($tr).find('td'),
          dataElementCount = $dataElements.length,
          $td, i;

      for (i = 0; i < rowData.length; i++) {
        if (i < dataElementCount) {
          $td = $($dataElements[i]);
          datum = rowData[i];
          $td.data('datum', datum);
          setFormattedData($td, datum, i);
        }
      }
    }

    function calculateSizeAndPosition() {
      tbodyPos = $tbody.position();
      tbodyHeight = $tbody.height();
    }

    function commitEditing() {
      var $input = $tbody.find('input');

      $input.each(function() {
        var $td = $(this).parent(),
            rowIndex = $td.parent().data('index'),
            colIndex = $td.data('index'),
            val = $(this).val();

        if (!isNaN(parseFloat(val)) && isFinite(val)) {
          val = parseFloat(val);
        }
        tableController.addDataToCell(rowIndex, colIndex, val);
        $td.empty().html(val);
      });

      alignColumnWidths();
      calculateSizeAndPosition();
    }

    function startEditing(rowIndex, colIndex) {
      var $td = $(getRowByIndex(rowIndex).find('td')[colIndex]),
          $oldInputs = $tbody.find('input'),
          data,
          $input,
          nextColIndex;

      if (!$td || (!columns[colIndex].editable) || ($td.find('input').length)) {
        return;
      }

      if ($oldInputs.length) {
        commitEditing();
      }

      data   = tableController.getDataInCell(rowIndex, colIndex);
      $input = $('<input class="editor-text">').val(data);

      $td.empty().append($input);

      alignColumnWidths();

      $input.bind('keydown', function(e) {
        var code = (e.keyCode ? e.keyCode : e.which);
        if(code === 13) {        // Enter
          commitEditing();
        } else if (code === 9){  // Tab
          commitEditing();
          // find and select next available cell
          nextColIndex = colIndex;
          while (++nextColIndex < columns.length) {
            if (columns[nextColIndex].editable) {
              startEditing(rowIndex, nextColIndex);
              break;
            }
          }
        }
        e.stopPropagation();
      });

      $input.on('blur', commitEditing);

      setTimeout(function(){
        $input.focus();
      }, 0);
    }

    api = {
      render: function() {
        var i, $title;
        $el = $('<div>');
        $table = $('<table>');
        $tbody = $('<tbody>');
        $titlerow = $('<tr class="header">');
        $thead = $('<thead>').append($titlerow);
        $table
          .append($thead)
          .append($tbody);
        renderColumnTitles();
        $tableWrapper = $('<div>')
          .addClass("table-wrapper")
          .append($table);
        $el.attr('id', id);
        if (title) {
          $title = $('<div>')
            .addClass("title")
            .text(title);
          $el.append($title);
        }
        $el.append($tableWrapper);
        for (i = 0; i < klasses.length; i++) {
          $el.addClass(klasses[i]);
        }
        if (tooltip) {
          $el.attr("title", tooltip);
        }
        if (width) {
          $el.css("width", width);
        }
        if (height) {
          $el.css("height", height);
        }
        $tbody.delegate("tr", "click", function(e) {
          var ri = getRowIndexFromRow($(e.currentTarget));
          if (!e.shiftKey && !e.metaKey) {
            clearSelection();
          }
          addSelection(ri);
          if (e.shiftKey) {
            fillSelection();
          }
        });
        $tbody.delegate("td", "click", function(e) {
          var $td      = $(e.currentTarget),
              rowIndex = $td.parent().data('index'),
              colIndex = $td.data('index');

          startEditing(rowIndex, colIndex);
        });
        calculateSizeAndPosition();
        return $el;
      },

      resize: function () {
        var remainingHeight;
        $table.height($tableWrapper.height());
        remainingHeight = $table.height() - ($thead.outerHeight(true));
        $tbody.height(remainingHeight - 6);
        alignColumnWidths();
        calculateSizeAndPosition();
      },

      clear: function () {
        $tbody.find('.data').remove();
      },

      appendDataRow: function (rowData, index) {
        appendSingleRow(rowData, index);
        setupBlankRow();
        scrollToBottom();
      },

      appendDataRows: function (rows, startIndex) {
        var index = startIndex;
        rows.forEach(function (row) {
          appendSingleRow(row, index++);
        });
        setupBlankRow();
        scrollToBottom();
      },

      removeDataRows: function (startIdx) {
        var $tr = $tbody.find('tr').filter(function() {
          var idx = $(this).data("index");
          return idx >= startIdx;
        });
        $tr.remove();
        setupBlankRow();
      },

      replaceDataRow: replaceDataRow,

      removeSelection: removeSelection,
      addSelection: addSelection,
      clearSelection: clearSelection,

      updateTable: function(opts) {
        columns     = opts.columns || columns;
        formatters  = opts.formatters || formatters;
        renderColumnTitles();
        formatNumericValues();
        alignColumnWidths();
        calculateSizeAndPosition();
      }
    };

    return api;
  };
});


/*global define, $*/

define('common/controllers/table-controller',['require','common/controllers/interactive-metadata','common/validator','common/views/table-view','common/listening-pool','common/controllers/data-set','common/controllers/help-icon-support'],function (require) {
  var metadata        = require('common/controllers/interactive-metadata'),
      validator       = require('common/validator'),
      TableView       = require('common/views/table-view'),
      ListeningPool   = require('common/listening-pool'),
      DataSet         = require('common/controllers/data-set'),
      helpIconSupport = require('common/controllers/help-icon-support'),
      tableControllerCount = 0;

  return function TableController(component, interactivesController) {
        // Public API.
    var controller,
        model,
        dataSet,
        listeningPool,
        view,
        $element,
        rowIndex,
        columns,
        formatters,
        headerData,
        properties,
        namespace = "tableController" + (++tableControllerCount);

    function initialize() {
      model = interactivesController.getModel();

      // Validate component definition, use validated copy of the properties.
      component = validator.validateCompleteness(metadata.table, component);

      properties = component.propertyColumns.slice();
      // dataTable has object-based properties, which dataSet doesn't support yet
      for (var i = 0; i < properties.length; i++) {
        if (properties[i].name) {
          properties[i] = properties[i].name;
        }
      }

      listeningPool = new ListeningPool(namespace);
      loadDataSet();

      generateColumnTitlesAndFormatters();
      rowIndex = 0;
      headerData = $.extend(true, [], component.headerData);

      view = new TableView({
        id: component.id,
        title: component.title,
        columns: columns,
        formatters: formatters,
        visibleRows: component.visibleRows,
        showBlankRow: component.showBlankRow,
        width: component.width,
        height: component.height,
        tooltip: component.tooltip,
        klasses: [ "interactive-table", "component" ]
      }, controller);

      $element = view.render();

      helpIconSupport(controller, component, interactivesController.helpSystem);

      // This will load serialized data (passed as "initialData") into the data set if available.
      // Otherwise data set will be just cleared. It will also call dataResetHandler(), so view
      // will be immediately updated.
      dataSet.resetData();
    }

    function loadDataSet () {
      // Get public data set (if its name is provided) or create own, private data set that will
      // be used only by this table.
      dataSet = component.dataSet ? interactivesController.getDataSet(component.dataSet) :
                                    new DataSet({
                                      name: component.id + "-autoDataSet",
                                      properties: properties.slice(),
                                      xProperty: component.xProperty,
                                      initialData: component.tableData,
                                      streamDataFromModel: component.streamDataFromModel,
                                      clearOnModelReset: component.clearOnModelReset
                                    }, interactivesController, true);

      // Register DataSet listeners.
      listeningPool.listen(dataSet, DataSet.Events.SAMPLE_ADDED, sampleAddedHandler);
      listeningPool.listen(dataSet, DataSet.Events.SAMPLE_CHANGED, sampleChangedHandler);
      listeningPool.listen(dataSet, DataSet.Events.DATA_RESET, dataResetHandler);
      listeningPool.listen(dataSet, DataSet.Events.DATA_TRUNCATED, dataTruncatedHandler);
      listeningPool.listen(dataSet, DataSet.Events.SELECTION_CHANGED, selectionChangedHandler);
    }

    function generateColumnTitlesAndFormatters() {
      var i, propertyName, columnDesc, propertyDescription, propertyTitle, unitAbrev;
      var editable, format;

      columns = [];
      formatters = [];

      if (component.indexColumn) {
        columns.push({name: "#", editable: false});
        formatters.push(d3.format("f"));
      }

      for(i = 0; i < component.propertyColumns.length; i++) {
        propertyTitle = null;
        editable = false;
        format = '.3r';

        if (typeof component.propertyColumns[i] === "string") {
          columnDesc = {name: component.propertyColumns[i]};
        } else {
          columnDesc = component.propertyColumns[i];
        }

        if (typeof model !== 'undefined') {
          propertyName = columnDesc.name;
          if (model.properties.hasOwnProperty(propertyName)) {
            propertyDescription = model.getPropertyDescription(propertyName);
            if (propertyDescription) {
              propertyTitle = propertyDescription.getLabel();
              unitAbrev = propertyDescription.getUnitAbbreviation();
              if (unitAbrev) {
                propertyTitle += ' (' + unitAbrev + ')';
              }
            }
          }
        }
        if (!propertyTitle) {
          propertyTitle = columnDesc.name;
          if (columnDesc.units) {
            propertyTitle += ' (' + columnDesc.units + ')';
          }
          editable = columnDesc.hasOwnProperty("editable") ? columnDesc.editable : true;
          format = columnDesc.hasOwnProperty("format") ? columnDesc.format : format;
        }
        columns.push({name: propertyTitle, editable: editable});
        formatters.push(d3.format(format));
      }
    }

    function updateTable() {
      generateColumnTitlesAndFormatters();
      view.updateTable({
        columns: columns,
        formatters: formatters
      });
    }

    function appendPropertyRow() {
      dataSet.appendDataPoint();
    }

    function selectionChangedHandler(evt) {
      var activeRow = evt.data;
      if (component.addNewRows) {
        view.clearSelection();
        view.addSelection(activeRow);
      } else {
        var data = dataSet.getData();
        view.replaceDataRow(nthRow(data, activeRow), 0);
      }
    }

    function data2row(dataPoint, index) {
      var dataRow = [];
      if (component.indexColumn) {
        if (index == null) {
          index = rowIndex;
        }
        dataRow.push(index);
      }
      properties.forEach(function (prop) {
        dataRow.push(dataPoint[prop]);
      });
      return dataRow;
    }

    function nthRow(data, index) {
      var dataRow = [];
      if (component.indexColumn) {
        dataRow.push(index);
      }
      properties.forEach(function (prop) {
        dataRow.push(data[prop][index]);
      });
      return dataRow;
    }

    function isEmpty(row) {
      for (var i = component.indexColumn ? 1 : 0, len = row.length; i < len; i++) {
        if (row[i] != null) return false;
      }
      return true;
    }

    function handleNewDataRow(dataPoint) {
      var dataRow = data2row(dataPoint);
      if (isEmpty(dataRow)) return;
      if (component.addNewRows) {
        view.appendDataRow(dataRow, rowIndex);
        rowIndex++;
      } else {
        view.replaceDataRow(dataRow, 0);
        rowIndex++;
      }
    }

    function sampleAddedHandler(evt) {
      handleNewDataRow(evt.data);
    }

    function sampleChangedHandler(evt) {
      var rowIndex = evt.data.index;
      var dataRow = data2row(evt.data.dataPoint, rowIndex);
      if (component.addNewRows) {
        view.replaceDataRow(dataRow, rowIndex);
      } else {
        view.replaceDataRow(dataRow, 0);
      }
    }

    function dataResetHandler(evt) {
      var data = evt.data;
      var length = dataSet.maxLength(properties);
      var dataRow;

      if (component.addNewRows) {
        var dataRows = [];
        rowIndex = 0;
        for (; rowIndex < length; rowIndex++) {
          dataRows.push(nthRow(data, rowIndex));
        }
        view.clear();
        view.appendDataRows(dataRows, 0);
      } else {
        dataRow = nthRow(data, length - 1);
        view.replaceDataRow(dataRow, 0);
        rowIndex = length;
      }
    }

    function dataTruncatedHandler(evt) {
      var dataLength = dataSet.maxLength(properties);
      rowIndex = dataLength;
      if (component.addNewRows) {
        view.removeDataRows(dataLength);
      } else {
        view.replaceDataRow(nthRow(evt.data, dataLength - 1), 0);
      }
    }

    function registerModelListeners() {
      /** -- Old methods not yet converted to new dataset

      Probably they shouldn't be converted at all. It's data set responsibility.

      model.on('stepBack.'+namespace, redrawCurrentStepPointer);
      model.on('stepForward.'+namespace, redrawCurrentStepPointer);
      model.on('seek.'+namespace, redrawCurrentStepPointer);
      model.on('play.'+namespace, function() {
        if (model.stepCounter() < tableData.length) {
          removeDataAfterStepPointer();
        }
      });
      model.on('invalidation.'+namespace, function() {
        replacePropertyRow();
      });

      **/
    }

    // Public API.
    controller = {
      /**
        Called by the interactives controller when the model finishes loading.
      */
      modelLoadedCallback: function() {
        model = interactivesController.getModel();
        registerModelListeners();
        updateTable();
      },

      resize: function () {
        if (view) view.resize();
      },

      getData: function(propArray) {
        var data = dataSet.getData();
        var result = {};
        propArray.forEach(function (prop) {
          result[prop] = data[prop];
        });
        return result;
      },

      /**
        Used when manually adding a row of property values to the table.
      */
      appendDataPropertiesToComponent: appendPropertyRow,

      addDataToCell: function (row, col, val) {
        // Index column is purely stored by view, it isn't present in DataSet.
        if (component.indexColumn) col--;
        var property = properties[col];

        if (row === rowIndex) {
          // Extend table when new non-empty data is added to "nonexistent" (in data model) row.
          if (val === "") return;
          var values = {};
          values[property] = val;
          dataSet.appendDataPoint(properties, values);
          return;
        }
        dataSet.editDataPoint(row, property, val);
      },

      getDataInCell: function (rowIndex, colIndex) {
        // Index column is purely stored by view, it isn't present in DataSet.
        if (component.indexColumn) colIndex--;
        var property = properties[colIndex];
        return  dataSet.getPropertyValue(rowIndex, property);
      },

      // Returns view container.
      getViewContainer: function () {
        return $element;
      },

      // Returns the view object.
      getView: function() {
        return view;
      },

      // Returns serialized component definition.
      serialize: function () {
        // start with the initial component definition.
        var result = $.extend(true, {}, component);
        // add headerData and tableData
        result.headerData = columns;
        if (!component.dataSet) {
          // Include data directly in component definition only when no external data set is
          // referenced by table. When some external data set is used, it will serialize data.
          result.tableData = dataSet.serializeData();
        }
        return result;
      }
    };

    initialize();

    // Return Public API object.
    return controller;
  };
});

/*global define: false */
define('common/benchmark/browser-detect',[],function () {
  // example userAgent strings:
  // chrome mobile: Mozilla/5.0 (Linux; Android 4.2.2; Galaxy Nexus Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.72 Mobile Safari/537.36

  var windows_platform_token = {
        "Windows NT 6.2": "Windows 8",
        "Windows NT 6.1": "Windows 7",
        "Windows NT 6.0": "Windows Vista",
        "Windows NT 5.2": "Windows Server 2003; Windows XP x64 Edition",
        "Windows NT 5.1": "Windows XP",
        "Windows NT 5.01": "Windows 2000, Service Pack 1 (SP1)",
        "Windows NT 5.0": "Windows 2000",
        "Windows NT 4.0": "Microsoft Windows NT 4.0"
      };

  function os_platform() {
    var match = navigator.userAgent.match(/\(([^)]+)\).*/);
    if (!match) { return "na"; }
    var systemInfo = match[1];
    var systemInfoArray = systemInfo.split("; ");

    if (systemInfoArray[0] === "Macintosh") {
      return systemInfoArray[1];
    } else if (systemInfoArray[0].match(/^Windows/)) {
      var token = navigator.userAgent.match(/\(.*?(Windows NT.+?)[;)]/),
          arch = "";
      if(systemInfo.match(/WOW64/)){
        arch = "64/32";
      } else if(systemInfo.match(/Win64; IA64/)){
        arch = "64";
      } else if(systemInfo.match(/Win64; x64/)){
        arch = "64";
      }
      return windows_platform_token[token[1]] + "/" + arch;
    } else if (systemInfoArray[0].match(/^X11/)) {
      return systemInfoArray.join('/');
    }

    return "na";
  }

  return {
    // Based on: http://detectmobilebrowsers.com/ + ipad|android|playbook|silk as we treat
    // tablets as mobile devices.
    isMobile: (function(a) { return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|android|playbook|silk|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i).test(a)||(/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i).test(a.substr(0,4)); })(navigator.userAgent||navigator.vendor||window.opera),

    what_browser: function what_browser() {
      var chromematch  = / (Chrome)\/(.*?) /,
          ffmatch      = / (Firefox)\/([0123456789ab.]+)/,
          safarimatch  = / AppleWebKit\/([0123456789.+]+) \(KHTML, like Gecko\) Version\/([0123456789.]+) (Safari)\/([0123456789.]+)/,
          iematch      = / (MSIE) ([0123456789.]+);/,
          operamatch   = /^(Opera)\/.+? Version\/([0123456789.]+)$/,
          iphonematch  = /.+?\((iPhone); CPU.+?OS .+?Version\/([0123456789._]+)/,
          ipadmatch    = /.+?\((iPad); CPU.+?OS .+?Version\/([0123456789._]+)/,
          ipodmatch    = /.+?\((iPod); CPU (iPhone.+?) like.+?Version\/([0123456789ab._]+)/,
          androidchromematch = /.+?(Android) ([0123456789.]+).*?; (.+?)\).+? Chrome\/([0123456789.]+)/,
          androidfirefoxmatch = /.+?(Android.+?\)).+? Firefox\/([0123456789.]+)/,
          androidmatch = /.+?(Android) ([0123456789ab.]+).*?; (.+?)\)/,
          match;

      match = navigator.userAgent.match(androidchromematch);
      if (match && match[1]) {
        return {
          browser: "Chrome for Android",
          version: match[4],
          oscpu: match[1] + "/" + match[2] + "/" + match[3]
        };
      }
      match = navigator.userAgent.match(chromematch);
      if (match && match[1]) {
        return {
          browser: match[1],
          version: match[2],
          oscpu: os_platform()
        };
      }
      match = navigator.userAgent.match(ffmatch);
      if (match && match[1]) {
        var buildID = navigator.buildID,
            buildDate = "";
        if (buildID && buildID.length >= 8) {
          buildDate = "(" + buildID.slice(0,4) + "-" + buildID.slice(4,6) + "-" + buildID.slice(6,8) + ")";
        }
        return {
          browser: match[1],
          version: match[2] + ' ' + buildDate,
          oscpu: os_platform()
        };
      }
      match = navigator.userAgent.match(androidfirefoxmatch);
      if (match && match[1]) {
        return {
          browser: "Firefox",
          version: match[2],
          oscpu: match[1]
        };
      }
      match = navigator.userAgent.match(androidmatch);
      if (match && match[1]) {
        return {
          browser: "Android",
          version: match[2],
          oscpu: match[1] + "/" + match[2] + "/" + match[3]
        };
      }
      match = navigator.userAgent.match(safarimatch);
      if (match && match[3]) {
        return {
          browser: match[3],
          version: match[2] + '/' + match[1],
          oscpu: os_platform()
        };
      }
      match = navigator.userAgent.match(iematch);
      if (match && match[1]) {
        var platform_match = navigator.userAgent.match(/\(.*?(Windows.+?); (.+?)[;)].*/);
        return {
          browser: match[1],
          version: match[2],
          oscpu: windows_platform_token[platform_match[1]] + "/" + navigator.cpuClass + "/" + navigator.platform
        };
      }
      match = navigator.userAgent.match(operamatch);
      if (match && match[1]) {
        return {
          browser: match[1],
          version: match[2],
          oscpu: os_platform()
        };
      }
      match = navigator.userAgent.match(iphonematch);
      if (match && match[1]) {
        return {
          browser: "Mobile Safari",
          version: match[2],
          oscpu: match[1] + "/" + "iOS" + "/" + match[2]
        };
      }
      match = navigator.userAgent.match(ipadmatch);
      if (match && match[1]) {
        return {
          browser: "Mobile Safari",
          version: match[2],
          oscpu: match[1] + "/" + "iOS" + "/" + match[2]
        };
      }
      match = navigator.userAgent.match(ipodmatch);
      if (match && match[1]) {
        return {
          browser: "Mobile Safari",
          version: match[3],
          oscpu: match[1] + "/" + "iOS" + "/" + match[2]
        };
      }
      return {
        browser: "",
        version: navigator.appVersion,
        oscpu:   ""
      };
    }
  };
});
/*global Lab, define: false, d3: false */
/*jshint loopfunc: true*/

/*
  ------------------------------------------------------------

  Simple benchmark runner and results generator

    see: https://gist.github.com/1364172

  ------------------------------------------------------------

  Runs benchmarks and generates the results in a table.

  Setup benchmarks to run in an array of objects with two properties:

    name: a title for the table column of results
    numeric: boolean, used to decide what columns should be used to calculate averages
    formatter: (optional) a function that takes a number and returns a formmatted string, example: d3.format("5.1f")
    run: a function that is called to run the benchmark and call back with a value.
         It should accept a single argument, the callback to be called when the
         benchmark completes. It should pass the benchmark value to the callback.

  Start the benchmarks by passing the table element where the results are to
  be placed and an array of benchmarks to run.

  Example:

    var benchmarks_table = document.getElementById("benchmarks-table");

    var benchmarks_to_run = [
      {
        name: "molecules",
        run: function(done) {
          done(mol_number);
        }
      },
      {
        name: "100 Steps (steps/s)",
        run: function(done) {
          modelStop();
          var start = +Date.now();
          var i = -1;
          while (i++ < 100) {
            model.tick();
          }
          elapsed = Date.now() - start;
          done(d3.format("5.1f")(100/elapsed*1000));
        }
      },
    ];

    benchmark.run(benchmarks_table, benchmarks_to_run)

  You can optionally pass two additional arguments to the run method: start_callback, end_callback

    function run(benchmarks_table, benchmarks_to_run, start_callback, end_callback)

  These arguments are used when the last benchmark test is run using the browsers scheduling and re-painting mechanisms.

  For example this test runs a model un the browser and calculates actual frames per second combining the
  model, view, and browser scheduling and repaint operations.

    {
      name: "fps",
      numeric: true,
      formatter: d3.format("5.1f"),
      run: function(done) {
        // warmup
        model.start();
        setTimeout(function() {
          model.stop();
          var start = model.get('time');
          setTimeout(function() {
            // actual fps calculation
            model.start();
            setTimeout(function() {
              model.stop();
              var elapsedModelTime = model.get('time') - start;
              done( elapsedModelTime / (model.get('timeStepsPerTick') * model.get('timeStep')) / 2 );
            }, 2000);
          }, 100);
        }, 1000);
      }
    }

  Here's an example calling the benchmark.run method and passing in start_callback, end_callback functions:

    benchmark.run(document.getElementById("model-benchmark-results"), benchmarksToRun, function() {
      $runBenchmarksButton.attr('disabled', true);
    }, function() {
      $runBenchmarksButton.attr('disabled', false);
    });

  The "Run Benchmarks" button is disabled until the browser finishes running thelast queued test.

  The first five columns in the generated table consist of:

    browser, version, cpu/os, date, and commit

  These columns are followed by a column for each benchmark passed in.

  Subsequent calls to: benchmark.run(benchmarks_table, benchmarks_to_run) will
  add additional rows to the table.

  A special second row is created in the table which displays averages of all tests
  that generate numeric results.

  Here are some css styles for the table:

    table {
      font: 11px/24px Verdana, Arial, Helvetica, sans-serif;
      border-collapse: collapse; }
    th {
      padding: 0 1em;
      text-align: left; }
    td {
      border-top: 1px solid #cccccc;
      padding: 0 1em; }

*/

define('common/benchmark/benchmark',['require','./browser-detect'],function (require) {
  var browser_detect = require('./browser-detect'),
      what_browser = browser_detect.what_browser,

      _isMobile = browser_detect.isMobile,
      _browser = browser_detect.what_browser(),

      average_row;

  function renderToTable(benchmarks_table, benchmarksThatWereRun, results) {
    var i = 0,
        results_row,
        result,
        col_number = 0,
        col_numbers = {},
        title_row,
        title_cells,
        len,
        rows = benchmarks_table.getElementsByTagName("tr");

    benchmarks_table.style.display = "";

    function add_column(title) {
      var title_row = benchmarks_table.getElementsByTagName("tr")[0],
          cell = title_row.appendChild(document.createElement("th"));

      cell.innerHTML = title;
      col_numbers[title] = col_number++;
    }

    function add_row(num_cols) {
      num_cols = num_cols || 0;
      var tr =  benchmarks_table.appendChild(document.createElement("tr")),
          i;

      for (i = 0; i < num_cols; i++) {
        tr.appendChild(document.createElement("td"));
      }
      return tr;
    }

    function add_result(name, content, row) {
      var cell;
      row = row || results_row;
      cell = row.getElementsByTagName("td")[col_numbers[name]];
      if (typeof content === "string" && content.slice(0,1) === "<") {
        cell.innerHTML = content;
      } else {
        cell.textContent = content;
      }
    }

    function update_averages() {
      var i, j,
          b,
          row,
          num_rows = rows.length,
          cell,
          cell_index,
          average_elements = average_row.getElementsByTagName("td"),
          total,
          average,
          genericDecimalFormatter = d3.format("5.1f"),
          genericIntegerFormatter = d3.format("f");

      function isInteger(i) {
        return Math.floor(i) === i;
      }

      for (i = 0; i < benchmarksThatWereRun.length; i++) {
        b = benchmarksThatWereRun[i];
        cell_index = col_numbers[b.name];
        if (b.numeric === false) {
          row = rows[2];
          cell = row.getElementsByTagName("td")[cell_index];
          average_elements[cell_index].innerHTML = cell.innerHTML;
        } else {
          total = 0;
          for (j = 2; j < num_rows; j++) {
            row = rows[j];
            cell = row.getElementsByTagName("td")[cell_index];
            total += (+cell.textContent);
          }
          average = total/(num_rows-2);
          if (b.formatter) {
            average = b.formatter(average);
          } else {
            if (isInteger(average)) {
              average = genericIntegerFormatter(total/(num_rows-2));
            } else {
              average = genericDecimalFormatter(total/(num_rows-2));
            }
          }
          average_elements[cell_index].textContent = average;
        }
      }
    }

    if (rows.length === 0) {
      add_row();
      add_column("browser");
      add_column("version");
      add_column("cpu/os");
      add_column("date");
      add_column("commit");
      add_column("branch");
      for (i = 0; i < benchmarksThatWereRun.length; i++) {
        add_column(benchmarksThatWereRun[i].name);
      }
      average_row = add_row(col_number);
      average_row.className = 'average';
    } else {
      title_row = rows[0];
      title_cells = title_row.getElementsByTagName("th");
      for (i = 0, len = title_cells.length; i < len; i++) {
        col_numbers[title_cells[i].innerHTML] = col_number++;
      }
    }

    results_row = add_row(col_number);
    results_row.className = 'sample';

    for (i = 0; i < 6; i++) {
      result = results[i];
      add_result(result[0], result[1]);
      add_result(result[0], result[1], average_row);
    }

    for(i = 6; i < results.length; i++) {
      result = results[i];
      add_result(result[0], result[1]);
    }
    update_averages();
  }

  function bench(benchmarks_to_run, resultsCallback, start_callback, end_callback) {
    var bencharks_queue = benchmarks_to_run.slice(),
        results = [],
        browser_info = what_browser(),
        formatter = d3.time.format("%Y-%m-%d %H:%M"),
        commit_link;

    results.push([ "browser", browser_info.browser]);
    results.push([ "version", browser_info.version]);
    results.push([ "cpu/os", browser_info.oscpu]);
    results.push([ "date", formatter(new Date())]);

    commit_link = "<a href='"+Lab.version.repo.commit.url+"' class='opens-in-new-window' target='_blank'>"+Lab.version.repo.commit.short_sha+"</a>";
    if (Lab.version.repo.dirty) {
      commit_link += " <i>dirty</i>";
    }
    results.push([ "commit", commit_link]);
    results.push([ "branch", Lab.version.repo.branch]);

    if (start_callback) start_callback();

    runBenchmark(bencharks_queue.shift());

    function runBenchmark(b) {
      b.run(doneCallback);

      function doneCallback(result) {
        if (b.formatter) {
          results.push([ b.name, b.formatter(result) ]);
        } else {
          results.push([ b.name, result ]);
        }

        if (bencharks_queue.length > 0) {
          runBenchmark(bencharks_queue.shift());
        } else {
          if (end_callback) end_callback();
          if (resultsCallback) resultsCallback(results);
        }
      }
    }

    return results;
  }

  function run(benchmarks_to_run, benchmarks_table, resultsCallback, start_callback, end_callback) {
    var results;
    bench(benchmarks_to_run, function(results) {
      renderToTable(benchmarks_table, benchmarks_to_run, results);
      resultsCallback(results);
    }, start_callback, end_callback);
    return results;
  }

  // Return Public API.
  return {
    /**
     * Browser description.
     */
    get browser() {
      return _browser;
    },
    /**
     * Triggers recalculation of browser description and returns the result.
     * Depreciated, use .browser property instead.
     */
    what_browser: function() {
      _browser = what_browser();
      return _browser;
    },
    get isMobile() {
      return _isMobile;
    },
    // run benchmarks, add row to table, update averages row
    run: function(benchmarks_to_run, benchmarks_table, resultsCallback, start_callback, end_callback) {
      run(benchmarks_to_run, benchmarks_table, resultsCallback, start_callback, end_callback);
    },
    // run benchmarks, return results in object
    bench: function(benchmarks_to_run, resultsCallback, start_callback, end_callback) {
      return bench(benchmarks_to_run, resultsCallback, start_callback, end_callback);
    },
    // run benchmarks, add row to table, update averages row
    renderToTable: function(benchmarks_table, benchmarksThatWereRun, results) {
      renderToTable(benchmarks_table, benchmarksThatWereRun, results);
    }
  };
});


define('common/url-helper',['require','lab.version','lab.config'],function (require) {
  var version  = require('lab.version');
  var config   = require('lab.config');

  var addParam = function(string, key, value) {
    if (string.length > 0) {
      return string + "&" + key + "=" + value;
    }
    return "?" + key + "=" + value;
  };

  return {
    getVersionedUrl: function () {
      if (config.versionedHome && version.repo.last_tag) {
        return config.versionedHome(version.repo.last_tag);
      }
      var host     = window.location.host;
      var path     = window.location.pathname;
      var search   = window.location.search;
      var protocol = window.location.protocol;
      search = addParam(search, 'show_data_warning', 'true');
      return protocol + "//" + host + path + search;
    }
  };
});

/*global define:false*/

define('common/controllers/parent-message-api',['require','common/benchmark/benchmark','common/url-helper','iframe-phone'],function(require) {
  var benchmark   = require('common/benchmark/benchmark');
  var urlHelper   = require('common/url-helper');
  var iframePhone = require('iframe-phone');

  // Defines the default postMessage API used to communicate with parent window (i.e., an embedder)
  return function(controller) {
    var model;
    // iframeEndpoint is a singleton (iframe can't have multiple parents).
    var iframeEndpoint = iframePhone.getIFrameEndpoint();

    function sendPropertyValue(propertyName) {
      iframeEndpoint.post('propertyValue', {
        name: propertyName,
        value: model.get(propertyName)
      });
    }

    function sendDataset(datasetName) {
      iframeEndpoint.post('dataset', {
        name: datasetName,
        value: controller.getDataSet(datasetName).serialize()
      });
    }

    // on message 'setFocus' call view.setFocus
    iframeEndpoint.addListener('setFocus', function() {
      var view = controller.modelController.modelContainer;
      if (view && view.setFocus) {
        view.setFocus();
      }
    });

    // on message 'getLearnerUrl' return urlHelper.getVersionedUrl()
    iframeEndpoint.addListener('getLearnerUrl', function() {
      iframeEndpoint.post('setLearnerUrl', urlHelper.getVersionedUrl());
    });

    // on message 'loadInteractive' call controller.loadInteractive
    iframeEndpoint.addListener('loadInteractive', function(content) {
      if (controller && controller.loadInteractive) {
        controller.loadInteractive(content);
      }
    });

    // on message 'loadModel' call controller.loadModel
    iframeEndpoint.addListener('loadModel', function(content) {
      if (controller && controller.loadModel) {
        controller.loadModel(content.modelId, content.modelObject);
      }
    });

    // on message 'getModelState' call and return controller.modelController.state()
    iframeEndpoint.addListener('getModelState', function() {
      if (controller && controller.modelController) {
        iframeEndpoint.post('modelState', controller.modelController.state());
      }
    });

    // on message 'getInteractiveState' call and return controller.serialize() result
    iframeEndpoint.addListener('getInteractiveState', function() {
      if (controller && controller.modelController) {
        iframeEndpoint.post('interactiveState', controller.serialize());
      }
    });

    // on message 'runBenchmarks' call controller.runBenchmarks
    iframeEndpoint.addListener('runBenchmarks', function() {
      var modelController, benchmarks;
      if (controller && controller.modelController) {
        modelController = controller.modelController;
        benchmarks = controller.benchmarks.concat(modelController.benchmarks);
        benchmark.bench(benchmarks, function(results) {
          console.log(results);
          iframeEndpoint.post('returnBenchmarks', {
            results: results,
            benchmarks: benchmarks
          });
        });
      }
    });

    // Listen for events in the model, and notify using message.post
    // uses D3 disaptch on model to trigger events
    // pass in message.properties ([names]) to also send model properties
    // in content object when triggering in parent Frame
    iframeEndpoint.addListener('listenForDispatchEvent', function(content) {
      var eventName    = content.eventName,
          properties   = content.properties,
          values       = {},
          i            = 0,
          propertyName = null;

      model.on(eventName, function() {
        if (properties) {
          for (i = 0 ; i < properties.length; i++) {
            propertyName = properties[i];
            values[propertyName] = model.get(propertyName);
          }
        }
        iframeEndpoint.post(eventName, values);
      });
    });

    var sendDatasetEvents = true;
    // Listen for events in a dataset, and notify using message.post
    // in content object when triggering in parent Frame
    iframeEndpoint.addListener('listenForDatasetEvent', function(content) {
      var eventName    = content.eventName,
          datasetName  = content.datasetName,
          dataset      = controller.getDataSet(datasetName);

      if (!dataset) { return; }
      console.log("registering listener on " + datasetName + ": " + eventName);

      dataset.on(eventName, function(evt) {
        if (sendDatasetEvents) {
          iframeEndpoint.post(datasetName + "-" + eventName, evt);
        }
      });
    });

    iframeEndpoint.addListener('sendDatasetEvent', function(content) {
      var eventName    = content.eventName,
          datasetName  = content.datasetName,
          data         = content.data,
          dataset      = controller.getDataSet(datasetName);

      if (!dataset) { return; }

      sendDatasetEvents = false;
      dataset.handleExternalEvent(eventName, data);
      sendDatasetEvents = true;
    });

    // Remove an existing Listener for events in the model
    iframeEndpoint.addListener('removeListenerForDispatchEvent', function(content) {
      model.on(content, null);
    });

    // on message 'getDataset' datasetName: return a 'dataset' message
    iframeEndpoint.addListener('getDataset', function(content) {
      sendDataset(content);
    });

    // on message 'get' propertyName: return a 'propertyValue' message
    iframeEndpoint.addListener('get', function(content) {
      sendPropertyValue(content);
    });

    // on message 'observe' propertyName: send 'propertyValue' once, and then every time
    // the property changes.
    iframeEndpoint.addListener('observe', function(content) {
      model.addPropertiesListener(content, function() {
        sendPropertyValue(content);
      });
      // Don't forget to send the initial value of the property too:
      sendPropertyValue(content);
    });

    // on message 'set' propertyName: set the relevant property
    iframeEndpoint.addListener('set', function(content) {
      model.set(content.name, content.value);
    });

    iframeEndpoint.addListener('tick', function(content) {
      model.tick(Number(content));
    });

    iframeEndpoint.addListener('play', function() {
      model.start();
    });

    iframeEndpoint.addListener('stop', function() {
      model.stop();
    });

    iframeEndpoint.addListener('reloadModel', function() {
      controller.reloadModel();
    });

    iframeEndpoint.addListener('reloadInteractive', function() {
      controller.reloadInteractive();
    });

    iframeEndpoint.initialize();

    controller.on('modelLoaded.parentMessageAPI', function() {
      iframeEndpoint.post('modelLoaded');
    });

    return {
      // REF FIXME: use scripting API object and avoid binding the model at all (as scripting
      // API is always guaranteed to have a current, valid model object).
      bindModel: function (newModel) {
        model = newModel;
      }
    };
  };
});

/*!
 * mustache.js - Logic-less {{mustache}} templates with JavaScript
 * http://github.com/janl/mustache.js
 */

/*global define: false*/

(function (root, factory) {
  if (typeof exports === "object" && exports) {
    factory(exports); // CommonJS
  } else {
    var mustache = {};
    factory(mustache);
    if (typeof define === "function" && define.amd) {
      define('mustache',mustache); // AMD
    } else {
      root.Mustache = mustache; // <script>
    }
  }
}(this, function (mustache) {

  var whiteRe = /\s*/;
  var spaceRe = /\s+/;
  var nonSpaceRe = /\S/;
  var eqRe = /\s*=/;
  var curlyRe = /\s*\}/;
  var tagRe = /#|\^|\/|>|\{|&|=|!/;

  // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
  // See https://github.com/janl/mustache.js/issues/189
  var RegExp_test = RegExp.prototype.test;
  function testRegExp(re, string) {
    return RegExp_test.call(re, string);
  }

  function isWhitespace(string) {
    return !testRegExp(nonSpaceRe, string);
  }

  var Object_toString = Object.prototype.toString;
  var isArray = Array.isArray || function (object) {
    return Object_toString.call(object) === '[object Array]';
  };

  function isFunction(object) {
    return typeof object === 'function';
  }

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

  var entityMap = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': '&quot;',
    "'": '&#39;',
    "/": '&#x2F;'
  };

  function escapeHtml(string) {
    return String(string).replace(/[&<>"'\/]/g, function (s) {
      return entityMap[s];
    });
  }

  function Scanner(string) {
    this.string = string;
    this.tail = string;
    this.pos = 0;
  }

  /**
   * Returns `true` if the tail is empty (end of string).
   */
  Scanner.prototype.eos = function () {
    return this.tail === "";
  };

  /**
   * Tries to match the given regular expression at the current position.
   * Returns the matched text if it can match, the empty string otherwise.
   */
  Scanner.prototype.scan = function (re) {
    var match = this.tail.match(re);

    if (match && match.index === 0) {
      var string = match[0];
      this.tail = this.tail.substring(string.length);
      this.pos += string.length;
      return string;
    }

    return "";
  };

  /**
   * Skips all text until the given regular expression can be matched. Returns
   * the skipped string, which is the entire tail if no match can be made.
   */
  Scanner.prototype.scanUntil = function (re) {
    var index = this.tail.search(re), match;

    switch (index) {
    case -1:
      match = this.tail;
      this.tail = "";
      break;
    case 0:
      match = "";
      break;
    default:
      match = this.tail.substring(0, index);
      this.tail = this.tail.substring(index);
    }

    this.pos += match.length;

    return match;
  };

  function Context(view, parent) {
    this.view = view == null ? {} : view;
    this.parent = parent;
    this._cache = { '.': this.view };
  }

  Context.make = function (view) {
    return (view instanceof Context) ? view : new Context(view);
  };

  Context.prototype.push = function (view) {
    return new Context(view, this);
  };

  Context.prototype.lookup = function (name) {
    var value;
    if (name in this._cache) {
      value = this._cache[name];
    } else {
      var context = this;

      while (context) {
        if (name.indexOf('.') > 0) {
          value = context.view;

          var names = name.split('.'), i = 0;
          while (value != null && i < names.length) {
            value = value[names[i++]];
          }
        } else {
          value = context.view[name];
        }

        if (value != null) break;

        context = context.parent;
      }

      this._cache[name] = value;
    }

    if (isFunction(value)) {
      value = value.call(this.view);
    }

    return value;
  };

  function Writer() {
    this.clearCache();
  }

  Writer.prototype.clearCache = function () {
    this._cache = {};
    this._partialCache = {};
  };

  Writer.prototype.compile = function (template, tags) {
    var fn = this._cache[template];

    if (!fn) {
      var tokens = mustache.parse(template, tags);
      fn = this._cache[template] = this.compileTokens(tokens, template);
    }

    return fn;
  };

  Writer.prototype.compilePartial = function (name, template, tags) {
    var fn = this.compile(template, tags);
    this._partialCache[name] = fn;
    return fn;
  };

  Writer.prototype.getPartial = function (name) {
    if (!(name in this._partialCache) && this._loadPartial) {
      this.compilePartial(name, this._loadPartial(name));
    }

    return this._partialCache[name];
  };

  Writer.prototype.compileTokens = function (tokens, template) {
    var self = this;
    return function (view, partials) {
      if (partials) {
        if (isFunction(partials)) {
          self._loadPartial = partials;
        } else {
          for (var name in partials) {
            self.compilePartial(name, partials[name]);
          }
        }
      }

      return renderTokens(tokens, self, Context.make(view), template);
    };
  };

  Writer.prototype.render = function (template, view, partials) {
    return this.compile(template)(view, partials);
  };

  /**
   * Low-level function that renders the given `tokens` using the given `writer`
   * and `context`. The `template` string is only needed for templates that use
   * higher-order sections to extract the portion of the original template that
   * was contained in that section.
   */
  function renderTokens(tokens, writer, context, template) {
    var buffer = '';

    // This function is used to render an artbitrary template
    // in the current context by higher-order functions.
    function subRender(template) {
      return writer.render(template, context);
    }

    var token, tokenValue, value;
    for (var i = 0, len = tokens.length; i < len; ++i) {
      token = tokens[i];
      tokenValue = token[1];

      switch (token[0]) {
      case '#':
        value = context.lookup(tokenValue);

        if (typeof value === 'object' || typeof value === 'string') {
          if (isArray(value)) {
            for (var j = 0, jlen = value.length; j < jlen; ++j) {
              buffer += renderTokens(token[4], writer, context.push(value[j]), template);
            }
          } else if (value) {
            buffer += renderTokens(token[4], writer, context.push(value), template);
          }
        } else if (isFunction(value)) {
          var text = template == null ? null : template.slice(token[3], token[5]);
          value = value.call(context.view, text, subRender);
          if (value != null) buffer += value;
        } else if (value) {
          buffer += renderTokens(token[4], writer, context, template);
        }

        break;
      case '^':
        value = context.lookup(tokenValue);

        // Use JavaScript's definition of falsy. Include empty arrays.
        // See https://github.com/janl/mustache.js/issues/186
        if (!value || (isArray(value) && value.length === 0)) {
          buffer += renderTokens(token[4], writer, context, template);
        }

        break;
      case '>':
        value = writer.getPartial(tokenValue);
        if (isFunction(value)) buffer += value(context);
        break;
      case '&':
        value = context.lookup(tokenValue);
        if (value != null) buffer += value;
        break;
      case 'name':
        value = context.lookup(tokenValue);
        if (value != null) buffer += mustache.escape(value);
        break;
      case 'text':
        buffer += tokenValue;
        break;
      }
    }

    return buffer;
  }

  /**
   * Forms the given array of `tokens` into a nested tree structure where
   * tokens that represent a section have two additional items: 1) an array of
   * all tokens that appear in that section and 2) the index in the original
   * template that represents the end of that section.
   */
  function nestTokens(tokens) {
    var tree = [];
    var collector = tree;
    var sections = [];

    var token;
    for (var i = 0, len = tokens.length; i < len; ++i) {
      token = tokens[i];
      switch (token[0]) {
      case '#':
      case '^':
        sections.push(token);
        collector.push(token);
        collector = token[4] = [];
        break;
      case '/':
        var section = sections.pop();
        section[5] = token[2];
        collector = sections.length > 0 ? sections[sections.length - 1][4] : tree;
        break;
      default:
        collector.push(token);
      }
    }

    return tree;
  }

  /**
   * Combines the values of consecutive text tokens in the given `tokens` array
   * to a single token.
   */
  function squashTokens(tokens) {
    var squashedTokens = [];

    var token, lastToken;
    for (var i = 0, len = tokens.length; i < len; ++i) {
      token = tokens[i];
      if (token) {
        if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
          lastToken[1] += token[1];
          lastToken[3] = token[3];
        } else {
          lastToken = token;
          squashedTokens.push(token);
        }
      }
    }

    return squashedTokens;
  }

  function escapeTags(tags) {
    return [
      new RegExp(escapeRegExp(tags[0]) + "\\s*"),
      new RegExp("\\s*" + escapeRegExp(tags[1]))
    ];
  }

  /**
   * Breaks up the given `template` string into a tree of token objects. If
   * `tags` is given here it must be an array with two string values: the
   * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
   * course, the default is to use mustaches (i.e. Mustache.tags).
   */
  function parseTemplate(template, tags) {
    template = template || '';
    tags = tags || mustache.tags;

    if (typeof tags === 'string') tags = tags.split(spaceRe);
    if (tags.length !== 2) throw new Error('Invalid tags: ' + tags.join(', '));

    var tagRes = escapeTags(tags);
    var scanner = new Scanner(template);

    var sections = [];     // Stack to hold section tokens
    var tokens = [];       // Buffer to hold the tokens
    var spaces = [];       // Indices of whitespace tokens on the current line
    var hasTag = false;    // Is there a {{tag}} on the current line?
    var nonSpace = false;  // Is there a non-space char on the current line?

    // Strips all whitespace tokens array for the current line
    // if there was a {{#tag}} on it and otherwise only space.
    function stripSpace() {
      if (hasTag && !nonSpace) {
        while (spaces.length) {
          delete tokens[spaces.pop()];
        }
      } else {
        spaces = [];
      }

      hasTag = false;
      nonSpace = false;
    }

    var start, type, value, chr, token, openSection;
    while (!scanner.eos()) {
      start = scanner.pos;

      // Match any text between tags.
      value = scanner.scanUntil(tagRes[0]);
      if (value) {
        for (var i = 0, len = value.length; i < len; ++i) {
          chr = value.charAt(i);

          if (isWhitespace(chr)) {
            spaces.push(tokens.length);
          } else {
            nonSpace = true;
          }

          tokens.push(['text', chr, start, start + 1]);
          start += 1;

          // Check for whitespace on the current line.
          if (chr == '\n') stripSpace();
        }
      }

      // Match the opening tag.
      if (!scanner.scan(tagRes[0])) break;
      hasTag = true;

      // Get the tag type.
      type = scanner.scan(tagRe) || 'name';
      scanner.scan(whiteRe);

      // Get the tag value.
      if (type === '=') {
        value = scanner.scanUntil(eqRe);
        scanner.scan(eqRe);
        scanner.scanUntil(tagRes[1]);
      } else if (type === '{') {
        value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1])));
        scanner.scan(curlyRe);
        scanner.scanUntil(tagRes[1]);
        type = '&';
      } else {
        value = scanner.scanUntil(tagRes[1]);
      }

      // Match the closing tag.
      if (!scanner.scan(tagRes[1])) throw new Error('Unclosed tag at ' + scanner.pos);

      token = [type, value, start, scanner.pos];
      tokens.push(token);

      if (type === '#' || type === '^') {
        sections.push(token);
      } else if (type === '/') {
        // Check section nesting.
        openSection = sections.pop();
        if (!openSection) {
          throw new Error('Unopened section "' + value + '" at ' + start);
        }
        if (openSection[1] !== value) {
          throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
        }
      } else if (type === 'name' || type === '{' || type === '&') {
        nonSpace = true;
      } else if (type === '=') {
        // Set the tags for the next time around.
        tags = value.split(spaceRe);
        if (tags.length !== 2) {
          throw new Error('Invalid tags at ' + start + ': ' + tags.join(', '));
        }
        tagRes = escapeTags(tags);
      }
    }

    // Make sure there are no open sections when we're done.
    openSection = sections.pop();
    if (openSection) {
      throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
    }

    return nestTokens(squashTokens(tokens));
  }

  mustache.name = "mustache.js";
  mustache.version = "0.7.3";
  mustache.tags = ["{{", "}}"];

  mustache.Scanner = Scanner;
  mustache.Context = Context;
  mustache.Writer = Writer;

  mustache.parse = parseTemplate;

  // Export the escaping function so that the user may override it.
  // See https://github.com/janl/mustache.js/issues/244
  mustache.escape = escapeHtml;

  // All Mustache.* functions use this writer.
  var defaultWriter = new Writer();

  /**
   * Clears all cached templates and partials in the default writer.
   */
  mustache.clearCache = function () {
    return defaultWriter.clearCache();
  };

  /**
   * Compiles the given `template` to a reusable function using the default
   * writer.
   */
  mustache.compile = function (template, tags) {
    return defaultWriter.compile(template, tags);
  };

  /**
   * Compiles the partial with the given `name` and `template` to a reusable
   * function using the default writer.
   */
  mustache.compilePartial = function (name, template, tags) {
    return defaultWriter.compilePartial(name, template, tags);
  };

  /**
   * Compiles the given array of tokens (the output of a parse) to a reusable
   * function using the default writer.
   */
  mustache.compileTokens = function (tokens, template) {
    return defaultWriter.compileTokens(tokens, template);
  };

  /**
   * Renders the `template` with the given `view` and `partials` using the
   * default writer.
   */
  mustache.render = function (template, view, partials) {
    return defaultWriter.render(template, view, partials);
  };

  // This is here for backwards compatibility with 0.4.x.
  mustache.to_html = function (template, view, partials, send) {
    var result = mustache.render(template, view, partials);

    if (isFunction(send)) {
      send(result);
    } else {
      return result;
    }
  };

}));


define('text!common/controllers/thermometer.tpl',[],function () { return '<div class="interactive-thermometer component" id="{{id}}">\n  <div class="thermometer-main-container">\n    <div class="thermometer">\n      <div class="thermometer-fill"></div>\n    </div>\n    <p class="label">{{labelText}}</p>\n  </div>\n  <div class="labels-container">\n    {{#labels}}\n      <span class="value-label" style="bottom: {{position}}">{{label}}</span>\n    {{/labels}}\n  </div>\n</div>';});

/*global define, $ */

define('common/controllers/thermometer-controller',['require','mustache','text!common/controllers/thermometer.tpl','common/controllers/interactive-metadata','common/validator','common/jquery-plugins'],function (require) {

  var mustache       = require('mustache'),
      thermometerTpl = require('text!common/controllers/thermometer.tpl'),
      metadata       = require('common/controllers/interactive-metadata'),
      validator      = require('common/validator');
      require('common/jquery-plugins');

  /**
    An 'interactive thermometer' object, that wraps a base Thermometer with a label for use
    in Interactives.

    Properties are:

     modelLoadedCallback:  Standard interactive component callback, called as soon as the model is loaded.
     getViewContainer:     DOM element containing the Thermometer div and the label div.
     getView:              Returns base Thermometer object, with no label.
  */
  return function ThermometerController(component, interactivesController) {
    var units,
        digits,
        // Returns scaled value using provided 'scale' and 'offset' component properties.
        scaleFunc,
        // Returns value between 0% and 100% using provided 'min' and 'max' component properties.
        normalize,

        labelIsReading,
        fitWidth,
        $elem,
        $thermometer,
        $thermometerFill,
        $bottomLabel,
        $labelsContainer,

        controller,
        model,

        updateLabel = function (temperature) {
          temperature = scaleFunc(temperature);
          $bottomLabel.text(temperature.toFixed(digits) + " " + units);
        },

        // Updates thermometer using model property. Used in modelLoadedCallback.
        // Make sure that this function is only called when model is loaded.
        updateThermometer = function () {
          var t = model.get('targetTemperature');
          $thermometerFill.css("height", normalize(scaleFunc(t)));
          if (labelIsReading) updateLabel(t);
        };

    //
    // Initialization.
    //
    function initialize() {
      var reading, offset, scale,
          view, labelText, labels,
          longestLabelIdx, maxLength,
          max, min, i, len;

      model = interactivesController.getModel();

      component = validator.validateCompleteness(metadata.thermometer, component);
      reading = component.reading;
      units = reading.units;
      offset = reading.offset;
      scale  = reading.scale;
      digits = reading.digits;
      min = component.min;
      max = component.max;

      scaleFunc = function (val) {
        return scale * val + offset;
      };

      normalize = function (val) {
        return ((val - min) / (max - min) * 100) + "%";
      };

      labelIsReading = component.labelIsReading;
      labelText = labelIsReading ? "" : "Thermometer";

      // Calculate view.
      view = {
        id: component.id,
        labelText: labelIsReading ? "" : "Thermometer"
      };
      // Calculate tick labels positions.
      labels = component.labels;
      maxLength = -Infinity;
      view.labels = [];
      for (i = 0, len = labels.length; i < len; i++) {
        view.labels.push({
          label: labels[i].label,
          position: normalize(scaleFunc(labels[i].value))
        });
        if (labels[i].label.length > maxLength) {
          maxLength = labels[i].label.length;
          longestLabelIdx = i;
        }
      }
      // Render view.
      $elem = $(mustache.render(thermometerTpl, view));
      // Save useful references.
      $thermometer = $elem.find(".thermometer");
      $thermometerFill = $elem.find(".thermometer-fill");
      $bottomLabel = $elem.find(".label");
      $labelsContainer = $elem.find(".labels-container");

      // Calculate size of the "labels container" div.
      // It's used to ensure that wrapping DIV ($elem) has correct width
      // so layout system can work fine. We have to explicitly set its
      // width, as absolutely positioned elements (labels) are excluded
      // from the layout workflow.
      maxLength = $elem.measure(function() {
        // Calculate width of the longest label in ems (!).
        return (this.width() / parseFloat(this.css("font-size"))) + "em";
      }, ".value-label:eq(" + longestLabelIdx + ")", interactivesController.interactiveContainer);
      $labelsContainer.css("width", maxLength);

      // Support custom dimensions. Implementation may seem unclear,
      // but the goal is to provide most obvious behavior for authors.
      // We can simply set height of the most outer container.
      // Thermometer will adjusts itself appropriately.
      $elem.css("height", component.height);
      // Width is more tricky.
      fitWidth = false;
      if (!/%$/.test(component.width)) {
        // When it's ems or px, its enough to set thermometer width.
        $thermometer.css("width", component.width);
      } else {
        // Whet it's defined in %, set width of the most outer container
        // to that value and thermometer should use all available space
        // (100% or 100% - labels width).
        $elem.css("width", component.width);
        fitWidth = true;
      }
    }

    // Public API.
    controller = {
      // No modelLoadeCallback is defined. In case of need:
      modelLoadedCallback: function () {
        if (model) {
          model.removeObserver('targetTemperature', updateThermometer);
        }
        model = interactivesController.getModel();
        // TODO: update to observe actual system temperature once output properties are observable
        model.addPropertiesListener('targetTemperature', updateThermometer);
        updateThermometer();
      },

      // Returns view container.
      getViewContainer: function () {
        return $elem;
      },

      resize: function () {
        var thermometerHeight = $elem.height() - $bottomLabel.height();
        $thermometer.height(thermometerHeight);
        $labelsContainer.height(thermometerHeight);
        if (fitWidth) {
          // When user sets width in %, it means that the most outer container
          // width is equal to this value and thermometer shape should try to
          // use maximum available space.
          $thermometer.width($elem.width() - $labelsContainer.width());
        }
      },

      // Returns serialized component definition.
      serialize: function () {
        // Return the initial component definition.
        // Displayed value is always defined by the model,
        // so it shouldn't be serialized.
        return component;
      }
    };

    initialize();

    // Return Public API object.
    return controller;
  };
});

/*global define, $ */

define('common/controllers/playback-controller',['require','common/inherit','common/layout/detect-font-change','common/controllers/interactive-component','common/views/select-box-view','underscore'],function (require) {

  var inherit              = require('common/inherit'),
      detectFontChange     = require('common/layout/detect-font-change'),
      InteractiveComponent = require('common/controllers/interactive-component'),
      SelectBoxView        = require('common/views/select-box-view'),
      _                    = require('underscore');

  function disable($el) {
    $el.attr('disabled', true).addClass('lab-disabled').css('cursor', 'default');
  }

  function enable($el) {
    $el.attr('disabled', false).removeClass('lab-disabled').css('cursor', 'pointer');
  }

  function enableWhen(condition, $el) {
    if (condition) {
      enable($el);
    } else {
      disable($el);
    }
  }

  function disableWhen(condition, $el) {
    enableWhen(!condition, $el);
  }

  /**
   * Playback controller.
   *
   * @constructor
   * @extends InteractiveComponent
   * @param {Object} component Component JSON definition.
   * @param {interactivesController} interactives controller that created this playback controller
   */
  function PlaybackController(component, interactivesController) {
    // Call super constructor.
    InteractiveComponent.call(this, "playback", component, interactivesController);

    this.$element.addClass("interactive-playback");

    this._modelStopped = true;
    this._modelPlayable = true;
    this._modelHasPlayed = false;
    this._dataAreAvailableForExport = false;
    this._timeDesc = null;
    this._model = null;
    this._scriptingAPI = null;
    this._interactivesController = interactivesController;
    // Font used by time display
    this._fontSpec = "2em " + interactivesController.fontFamily;

    detectFontChange({
      font: interactivesController.fontFamily,
      onchange: this._updateClockVisibility.bind(this)
    });
  }
  inherit(PlaybackController, InteractiveComponent);

  // These method implementations depend on the controlButtonStyle
  var controlButtonMethods = {
    video: {
      createControls: function() {
        var scriptingAPI = this._scriptingAPI;
        var i18n = this._interactivesController.i18n;

        this.$element.empty();
        this.$element.removeClass('text').addClass('video');

        /** @private */
        this._$reset = $('<button class="reset"><i class="icon-step-backward"></i></button>').appendTo(this.$element);
        /** @private */
        this._$playPause = $('<button class="play-pause"><i class="icon-play"></i><i class="icon-pause"></i></button>').appendTo(this.$element);
        /** @private */
        this._$timeDisplay = $('<span class="time-display">').appendTo(this._$playPause);

        // Canvas is much faster that native HTML text, especially on mobile devices. See:
        // https://www.pivotaltracker.com/story/show/58879086
        /** @private */
        this._$timeCanvas = $('<canvas>').appendTo(this._$timeDisplay);
        /** @private */
        this._timeCtx = this._$timeCanvas[0].getContext("2d");

        /** @private */
        this._$stepBackward = $('<button class="step"><i class="icon-backward"></i></button>').insertBefore(this._$playPause);
        /** @private */
        this._$stepForward = $('<button class="step"><i class="icon-forward"></i></button>').insertAfter(this._$playPause);

        this._$reset.after('<div class="spacer reset">');
        this._$stepBackward.after('<div class="spacer step">');
        this._$stepForward.before('<div class="spacer step">');

        // Bind click handlers.
        this._$reset.on("click", scriptingAPI.reloadModel);

        this._$playPause.on("click", function() {
          if (this._modelStopped) {
            if (this._modelPlayable) {
              scriptingAPI.start();
            }
          } else {
            scriptingAPI.stop();
          }
        }.bind(this));

        this._$stepBackward.on("click", scriptingAPI.stepBack);
        this._$stepForward.on("click", scriptingAPI.stepForward);

        this._$playPause.attr("title", i18n.t("banner.video_play_pause_tooltip"));
        this._$reset.attr("title", i18n.t("banner.video_reset_tooltip"));
        this._$stepBackward.attr("title", i18n.t("banner.video_step_back_tooltip"));
        this._$stepForward.attr("title", i18n.t("banner.video_step_forward_tooltip"));
      },

      updateButtonStates: function(stopped, playable) {
        var playing = ! stopped;
        this._$playPause.toggleClass('playing', playing);
        disableWhen(stopped && ! playable, this._$playPause);
      },

      updateControlButtonChoices: function(mode) {
        var $buttons;

        if (!mode) { // mode === "" || mode === null || mode === false
          this.$element.find(".step, .reset, .play-pause").addClass("hidden");
        } else if (mode === "play") {
          this.$element.find(".play-pause").removeClass("hidden");
          this.$element.find(".spacer, .step, .reset").addClass("hidden");
        } else if (mode === "reset") {
          this.$element.find(".reset").removeClass("hidden");
          this.$element.find(".spacer, .play-pause, .step").addClass("hidden");
        } else if (mode === "play_reset") {
          this.$element.find(".spacer, .play-pause, .reset").removeClass("hidden");
          this.$element.find(".step").addClass("hidden");
        } else if (mode === "play_reset_step") {
          this.$element.find(".spacer, .step, .reset, .play-pause").removeClass("hidden");
        }
        $buttons = this.$element.find("button");
        $buttons.removeClass("first");
        $buttons.removeClass("last");
        $buttons = $buttons.not(".hidden");
        $buttons.first().addClass("first");
        $buttons.last().addClass("last");
      },

      setClockVisibility: function(showClock) {
        if (showClock) {
          this._$playPause.addClass("with-clock");
          // Update 'displayTime' description (used for formatting).
          this._timeDesc =  this._model.getPropertyDescription("displayTime");
          // Update clock immediately.
          this._timeChanged();
        } else {
          this._$playPause.removeClass("with-clock");
        }
      },

      setClockValue: function(value) {
        // Canvas is much faster that native HTML text, especially on mobile devices. See:
        // https://www.pivotaltracker.com/story/show/58879086
        this._timeCtx.clearRect(0, 0, this._canvWidth, this._canvHeigth);
        this._timeCtx.fillText(
          this._timeDesc.format(value), this._canvWidth, this._canvHeigth * 0.85);
      }
    },

    text: {
      createControls: function() {
        var scriptingAPI = this._scriptingAPI;
        var i18n = this._interactivesController.i18n;

        this.$element.empty();
        this.$element.removeClass('video').addClass('text');

        this._$start = $('<button class="start">').text(i18n.t("banner.text_start")).appendTo(this.$element);
        this._$stop = $('<button class="stop">').text(i18n.t("banner.text_stop")).appendTo(this.$element);
        this._$reset = $('<button class="reset">').text(i18n.t("banner.text_reset")).appendTo(this.$element);

        // Bind click handlers
        this._$reset.on('click', scriptingAPI.reloadModel);
        this._$start.on('click', scriptingAPI.start);
        this._$stop.on('click', scriptingAPI.stop);

        this._$start.attr("title", i18n.t("banner.text_start_tooltip"));
        this._$stop.attr("title",  i18n.t("banner.text_stop_tooltip"));
        this._$reset.attr("title", i18n.t("banner.text_reset_tooltip"));
      },

      updateButtonStates: function(stopped, playable) {
        disableWhen(stopped, this._$stop);
        enableWhen(playable, this._$start);
      },

      updateControlButtonChoices: function(mode) {
        if (!mode) { // mode === "" || mode === null || mode === false
          this.$element.find(".reset, .start, .stop").addClass("hidden");
        } else if (mode === "play") {
          this.$element.find(".start, .stop").removeClass("hidden");
          this.$element.find(".reset").addClass("hidden");
        } else if (mode === "reset") {
          this.$element.find(".reset").removeClass("hidden");
          this.$element.find(".start, .stop").addClass("hidden");
        } else if (mode === "play_reset") {
          this.$element.find(".start, .stop, .reset").removeClass("hidden");
        } else {
          // no play_reset_step support for text style buttons, yet.
          throw new Error("controlButtons option \"" + mode +
            "\" is not understood or is not compatible with controlButtonStyle \"text\"");
        }
      },

      setClockVisibility: function() {
        // noop
      },

      setClockValue: function() {
        // noop
      }
    },

    codap: {
      createControls: function() {
        var scriptingAPI = this._scriptingAPI;
        var i18n = this._interactivesController.i18n;

        this.$element.removeClass('video').addClass('text wide');
        this.$element.empty();

        this._$start = $('<button class="start">').
          text(i18n.t('banner.text_start')).
          attr("title", i18n.t('banner.text_start_tooltip')).
          appendTo(this.$element);

        this._$stop = $('<button class="stop">').
          text(i18n.t('banner.text_stop')).
          attr('title', i18n.t('banner.text_start_tooltip')).
          appendTo(this.$element);

        this._$analyzeData = $('<button class="analyze-data">').
          text(i18n.t('banner.text_analyze_data')).
          attr('title', i18n.t('banner.text_analyze_data_tooltip')).
          appendTo(this.$element);

        this._$newRun = $('<button class="new-run">').
          text(i18n.t('banner.text_new_run')).
          attr('title', i18n.t('banner.text_new_run_tooltip')).
          appendTo(this.$element);

        // Bind click handlers
        this._$start.on('click', scriptingAPI.start);
        this._$stop.on('click', scriptingAPI.stop);
        this._$analyzeData.on('click', function() {
          scriptingAPI.exportData();
          // Export controller may or may not want us to wait for a new run before re-enabling the
          // analyze data button:
          this._dataAreAvailableForExport = scriptingAPI.dataAreAvailableForExport();
          if ( ! this._dataAreAvailableForExport ) {
            disable($(this));
          }
        });

        this._$newRun.on('click', function() {
          scriptingAPI.reloadModel({ cause: 'new-run' });
        });
      },

      updateButtonStates: function(stopped, playable, hasPlayed, dataAreAvailableForExport) {
        disableWhen(hasPlayed, this._$start);
        disableWhen(stopped, this._$stop);
        enableWhen(hasPlayed && stopped, this._$newRun);
        enableWhen(dataAreAvailableForExport, this._$analyzeData);
      },

      updateControlButtonChoices: function(mode) {
        // mode is one of: null, 'play', 'reset', 'play_reset', 'play_reset_step'
        // only show start/stop buttons in 'play', 'play_reset', 'play_reset_step'
        // only show new-run button if reset button is requested AND play button is requested:
        // (i.e., in play_reset and play_reset_step)

        if (mode && mode.indexOf('play') >= 0) {
          this._$start.show();
          this._$stop.show();
          if (mode.indexOf('reset') >= 0) {
            this._$newRun.show();
          } else {
            this._$newRun.hide();
          }
        } else {
          this._$start.hide();
          this._$stop.hide();
          this._$newRun.hide();
        }
      },

      setClockVisibility: function() {
        // noop
      },

      setClockValue: function() {
        // noop
      }
    }
  };

  PlaybackController.prototype._createControls = function() {
    this._controlButtonMethods.createControls.apply(this, arguments);
    this._updateButtonStates();
    this._fitButtons();
  };

  PlaybackController.prototype._updateButtonStates = function() {
    this._controlButtonMethods.updateButtonStates.call(this,
       this._modelStopped, this._modelPlayable, this._modelHasPlayed, this._dataAreAvailableForExport );
  };

  PlaybackController.prototype._updateControlButtonChoices = function() {
    this._controlButtonMethods.updateControlButtonChoices.apply(this, arguments);
    this._fitButtons();
  };

  PlaybackController.prototype._setClockVisibility = function() {
    this._controlButtonMethods.setClockVisibility.apply(this, arguments);
  };

  PlaybackController.prototype._setClockValue = function() {
    this._controlButtonMethods.setClockValue.apply(this, arguments);
  };

  PlaybackController.prototype._updateCachedSimulationState = function() {
    var modelStopped = this._model.isStopped();
    // Coerce undefined to *true* for models that don't have isPlayable property
    var modelPlayable = this._model.properties.isPlayable === false ? false : true;
    var modelHasPlayed = this._model.properties.hasPlayed;
    var dataAreAvailableForExport = this._scriptingAPI.dataAreAvailableForExport();

    // Update button states only if modelStopped/modelPlayable actually changed. (Since they're
    // model properties, we are called every tick, unfortunately -- the optimization assumption
    // made by PropertySupport is that all model properties are *physics* properties which are
    // almost certain to change every tick, so it doesn't check to see if they really changed.)
    // update-button-states adds and removes classes, which at the very least adds a distracting
    // entry to Dev Tools timeline view every tick.
    if (modelStopped !== this._modelStopped ||
        modelPlayable !== this._modelPlayable ||
        modelHasPlayed !== this._modelHasPlayed ||
        dataAreAvailableForExport !== this._dataAreAvailableForExport) {
      this._modelStopped = modelStopped;
      this._modelPlayable = modelPlayable;
      this._modelHasPlayed = modelHasPlayed;
      this._dataAreAvailableForExport = dataAreAvailableForExport;

      return true;
    }
    return false;
  };

  /**
   * Updates play / pause button.
   * @private
   */
  PlaybackController.prototype._simulationStateChanged = function () {
    if (this._updateCachedSimulationState()) {
      this._updateButtonStates();
    }
  };

  /**
   * Updates time display.
   * @private
   */
  PlaybackController.prototype._timeChanged = function () {
    if (this._model.properties.showClock) {
      this._setClockValue(this._model.properties.displayTime);
    }
  };

  PlaybackController.prototype._useDurationChanged = function() {
    // Reminder. Output properties are notified every tick (due to obsolete assumption that they
    // are necessarily physical properties.)
    // Thus, dirty check before doing anything.

    if (this._useDuration !== this._model.properties.actualUseDuration) {
      this._useDuration = this._model.properties.actualUseDuration;
      this._updateDurationControl();
    }
  };

  PlaybackController.prototype._updateDurationControl = function() {
    var model = this._model;
    var useDuration = this._useDuration;
    var durationOptions = model.properties.durationOptions;
    var actualDuration = model.properties.actualDuration;
    var selectOptions = [];

    if (this.durationControl) {
      this.durationControl.destroy();
      this.durationControl = null;
    }

    if (useDuration && durationOptions.length > 0) {

      durationOptions.forEach(function(duration) {
        // Sneak actualDuration in before 'duration', if that's where it belongs in the sorted list
        // and it was not previously added.
        if (actualDuration < duration &&
            (selectOptions.length === 0 || actualDuration > selectOptions[selectOptions.length-1].value) ) {

          selectOptions.push({
            value: actualDuration,
            text: model.formatTime(actualDuration),
            selected: true
          });
        }

        selectOptions.push({
          value: duration,
          text: model.formatTime(duration),
          selected: duration === actualDuration
        });
      });

      this.durationControl = new SelectBoxView({
        id: 'duration-control',
        options: selectOptions,
        onChange: function(option) {
          // use this._model here, not the possibly-stale closure value 'model'!
          this._model.properties.requestedDuration = option.value;
        }.bind(this)
      });

      this.durationControl.render(this.$element.parent());
      this.durationControl.$element.addClass('duration-control interactive-pulldown component component-spacing');
      this.$element.parent().append(this.durationControl.$element);
    }

    this._fitButtons();
  };

  /**
   * Updates playback controller mode (none, "play", "play_reset" or "play_reset_step").
   * @private
   */
  PlaybackController.prototype._controlButtonChoicesChanged = function () {
    this._updateControlButtonChoices(this._model.properties.controlButtons);
  };

  /**
   * Updates playback controller style (currently, "video" or "text")
   * @private
   */
  PlaybackController.prototype._controlButtonStyleChanged = function () {
    // To handle model types whose metadata don't define controlButtonStyle, default to 'video' here
    var style = this._model.properties.controlButtonStyle || 'video';
    if (this.controlButtonStyle === style) {
      return;
    }
    this.controlButtonStyle = style;
    this.$element.empty();

    if (!controlButtonMethods[style]) {
      throw new Error("Unknown controlButtonStyle \"" + style + "\"");
    }

    this._controlButtonMethods = controlButtonMethods[style];
    this._updateCachedSimulationState();
    this._createControls();
    this._controlButtonChoicesChanged();
  };

  PlaybackController.prototype._updateClockVisibility = function() {
    this._setClockVisibility(this._model.properties.showClock);
  };

  /*
    Adjusts the font-size of the playback buttons so that they fit within the available
    width. The default font-size (set by the interactive's layout engine) is honored by default,
    but if using this size would cause the buttons to overflow our containing element, the
    relative font-size of the button container is adjusted so that they just fit. (The button
    widths are specified in ems.)

    A minimum font-size of 10px is used; when this might cause the buttons to overflow, the buttons
    are srunched together (by use of the 'scrunched-buttons' CSS class).

    This is a relatively "heavy" operation involving DOM manipulation and relayout, so
    it should not be called during a window resize. Instead, call _fitButtonsOnResize, which
    uses a debounced version of this method which delays until the end of the first 200ms
    window that passes without a resize event.
  */
  PlaybackController.prototype._fitButtons = function() {
    // Get the width of the element we need to fit into
    var parentWidth = this.$element.parent().width();
    var MIN_FONT_SIZE = 10; // px
    var relativeFontSize;
    var absoluteFontSize;
    var fontSizeStyle;

    var $durationControl = this.durationControl && this.durationControl.$element;

    // Temporarily undo any positioning that prevents overlap of buttons & duration control
    this.$element.css('left', '');

    // Get the width the buttons would have in the absence of the font-sizing and
    // margin adjustments that may have been previously applied by this method.
    var elementWidth = this.$element.measure(function() {
      // account for the width of the duration dropdown
      if ($durationControl) {
        this.prepend($durationControl.clone());
      }
      this.removeClass('scrunched-buttons');
      return this.width();
    }, null, $('#bottom-bar'));

    this.$element.parent().css('fontSize', '');

    if (elementWidth < parentWidth) {
      this.$element.removeClass('scrunched-buttons');
    } else {
      relativeFontSize = parentWidth / elementWidth;
      absoluteFontSize = parseFloat(this.$element.parent().css('font-size')) * relativeFontSize;
      fontSizeStyle = absoluteFontSize < MIN_FONT_SIZE ? (MIN_FONT_SIZE + 'px') : (relativeFontSize + 'em');
      this.$element.parent().css('fontSize', fontSizeStyle);

      // Remove scrunched-buttons before testing width...
      this.$element.removeClass('scrunched-buttons');

      // and then add it back if needed to make the buttons (more likely to) fit.
      // (The magic 10 is a guesstimate of the allowable slop/margin on the buttons; a
      // more precise value could be derived from button's calculated margin, but that
      // seems like overkill.)
      if (this.$element.width() - 10 >= parentWidth) {
        this.$element.addClass('scrunched-buttons');
      }
    }

    // Finally, make room for the duration control. Need to make sure left margin of leftmost button
    // doesn't bump into it, however.
    if ($durationControl) {
      var $b = this.$element.find('button:visible').first();

      if ($b.length > 0) {
        var right = $b.offset().left - parseFloat($b.css('padding-left')) - parseFloat($b.css('margin-left'));
        var left = $durationControl.offset().left + $durationControl.outerWidth(true);

        if (right < left) {
          this.$element.css('left', left - right + 'px');
        }
      }
    }
  };

  PlaybackController.prototype._fitButtonsOnResize = function() {
    if ( ! this._debouncedFitButtons ) {
      this._debouncedFitButtons = _.debounce(this._fitButtons.bind(this), 200);
    }

    // Prevent visual chaos by "locking" the playback control's fontsize to its current px
    // value while the interactive font-size changes during the resize operation.
    this.$element.parent().css('fontSize', this.$element.parent().css('fontSize'));

    // After 500ms of no resize events, set the playback controls' font-size so they fit
    this._debouncedFitButtons();
  };

  /**
   * Implements optional callback supported by Interactive Controller.
   */
  PlaybackController.prototype.modelLoadedCallback = function () {

    this._model = this._interactivesController.getModel();
    this._scriptingAPI = this._interactivesController.getScriptingAPI().api;

    var simulationStateChanged = this._simulationStateChanged.bind(this);

    // Update play / pause button.
    // Use event namespace to let multiple playbacks work fine with one model.
    this._model.on('play.' + this.component.id, simulationStateChanged);
    this._model.on('stop.' + this.component.id, simulationStateChanged);
    this._model.addObserver('isPlayable', simulationStateChanged);
    this._model.addObserver('showClock', this._updateClockVisibility.bind(this));
    this._model.addObserver('displayTime', this._timeChanged.bind(this));

    // Update which controls/style to display
    this._model.addObserver('actualUseDuration', this._useDurationChanged.bind(this));
    this._model.addObserver('controlButtons', this._controlButtonChoicesChanged.bind(this));
    this._model.addObserver('controlButtonStyle', this._controlButtonStyleChanged.bind(this));

    this._useDuration = null;
    this._useDurationChanged();
    this._controlButtonStyleChanged();
    this._controlButtonChoicesChanged();
    this._simulationStateChanged();
    this._updateClockVisibility();
  };

  /**
   * Implements optional callback supported by Interactive Controller.
     (*Could* be dispatched to controlButtonMethods, but is that really necessary?)
   */
  PlaybackController.prototype.resize = function () {

    this._fitButtonsOnResize();

    if ( !this._$timeCanvas ) {
      return;
    }

    // Oversample canvas, so text will look good on Retina-like displays.
    this._canvWidth = this._$timeCanvas.width() * 2;
    this._canvHeigth = this._$timeCanvas.height() * 2;
    this._$timeCanvas.attr("width", this._canvWidth);
    this._$timeCanvas.attr("height", this._canvHeigth);

    this._timeCtx.font = this._fontSpec;
    this._timeCtx.fillStyle = "#939598";
    this._timeCtx.textAlign = "right";

    this._updateClockVisibility();
  };

  return PlaybackController;
});

/*global define */

define('common/controllers/div-controller',['require','common/inherit','common/controllers/interactive-component','common/alert'],function (require) {

  var inherit              = require('common/inherit'),
      InteractiveComponent = require('common/controllers/interactive-component'),
      alert                = require('common/alert');

  /**
   * Simplest component controller which just inherits from InteractiveComponent, simply
   * creating a div element. Component can have dimensions, css classes and on onClick
   * function.
   * @param {Object} component Component JSON definition.
   * @param {ScriptingAPI} scriptingAPI
   * @param {InteractiveController} controller
   */
  function DivController(component, scriptingAPI, controller) {
    // Call super constructor.
    InteractiveComponent.call(this, "div", component, scriptingAPI, controller);
    var content = component.content;
    var divController = this;
    if (component.url) {
      // make sure the user sets the width and height because otherwise the layout
      // will be broken
      if( component.width === "auto" || component.height === "auto") {
        alert("This interactive has a remote div component.\n"+
              "The width and/or height is not set.\n"+
              "Please set both the width and height.");
      }


      $.ajax(component.url, {
        dataType: "html",
        complete: function (data){
          divController.$element.append(data.responseText);
        }
      });
    } else {
      if (content && content.join) {
        content = content.join("\n");
      }
      this.$element.append(content);
    }
  }
  inherit(DivController, InteractiveComponent);

  return DivController;
});

/*global define, $ */
define('common/controllers/help-system',['require','common/markdown-to-html','common/dispatch-support'],function (require) {

  var markdownToHTML  = require("common/markdown-to-html"),
      DispatchSupport = require("common/dispatch-support"),

      OVERLAY_MY = [
        "center bottom",
        "left center",
        "center top",
        "right center"
      ],

      OVERLAY_AT = [
        "center top-5",
        "right+5 center",
        "center bottom+5",
        "left-5 center"
      ],

      IS_BOUNDING_BOX = "lab-is-bounding-box";

  return function HelpSystem(helpTips, $container) {
    var api,
        dispatch = new DispatchSupport("start", "stop"),
        isActive = false,
        tipIdx = -1,
        $tip,
        $instructions,
        overlays = [];

    function showTip() {
      var def = helpTips[tipIdx],
          $component,
          overlayHeight,
          offset;

      if (!def) return;
      // Make sure that focus is active so keyboard handlers work fine.
      $tip.focus();
      // Update content.
      $tip.html(markdownToHTML(def.text));
      // Position.
      if (def.component) {
        $component = getComponent(def.component);
        overlayHeight = $component.outerHeight() + 10; // + 5+ 5 => take a loot at OVERLAY_AT values.
        offset = parseFloat($tip.css("font-size"));
        $tip.position({
          of: $component,
          collision: "flipfit flipfit",
          within: $container,
          // Arrow's height depends on font-size (as it's defined in ems).
          my: "left-" + (offset * 4) + " top+" + offset,
          at: "right bottom",
          using: function(position, feedback) {
            var eLeft  = feedback.element.left,
                eWidth = feedback.element.width,
                tLeft  = feedback.target.left,
                tWidth = feedback.target.width,
                $arrow, leftOffset;
            $(this).css(position);
            $arrow = $("<div>")
              .addClass("lab-help-arrow")
              .addClass(feedback.vertical)
              .appendTo(this);
            if (tLeft > eLeft) {
              leftOffset = tLeft - eLeft + tWidth / 2;
              leftOffset = Math.max(eWidth * 0.1, Math.min(eWidth * 0.9, leftOffset));
              $arrow.css("left", leftOffset);
            }
          }
        });
        overlays.forEach(function ($overlay, idx) {
          // Set custom height of left and right overlays.
          if (idx === 1 || idx === 3) $overlay.css("height", overlayHeight);
          $overlay.position({
            of: $component,
            collision: "none none",
            my: OVERLAY_MY[idx],
            at: OVERLAY_AT[idx]
          });
        });
        if ($component.data(IS_BOUNDING_BOX)) {
          // Cleanup.
          $component.remove();
        }
      } else {
        $tip.position({
          of: $container,
          collision: "flipfit flipfit",
          within: $container,
          my: "center center",
          at: "center center"
        });
        overlays.forEach(function ($overlay, idx) {
          $overlay.position({
            of: $container,
            collision: "none none",
            // Position all overlays outside the container except from one (avoid alpha channel
            // summing).
            my: idx ? "left top" : "center center",
            at: idx ? "right bottom" : "center center"
          });
        });
      }
    }

    function getComponent(compDef) {
      var $component;
      if (compDef === "model") {
        $component = $("#model-container");
      } else if (typeof compDef === "string") {
        $component = $("#" + compDef).closest(".component");
      } else { // array
        $component = $(compDef.map(function (id) { return "#" + id; }).join(", ")).closest(".component");
        $component = getBoundingBox($component);
      }
      return $component;
    }

    function getBoundingBox($elements) {
      var left = [],
          right = [],
          top = [],
          bottom = [],
          minLeft, maxRight, minTop, maxBottom, $bb;

      $elements.each(function () {
        $el = $(this);
        pos = $el.offset();
        contPos = $container.offset();
        left.push(pos.left - contPos.left);
        right.push(pos.left - contPos.left + $el.width());
        top.push(pos.top - contPos.top);
        bottom.push(pos.top - contPos.top + $el.height());
      });

      minLeft = Math.min.apply(null, left);
      maxRight = Math.max.apply(null, right);
      minTop = Math.min.apply(null, top);
      maxBottom = Math.max.apply(null, bottom);

      $bb = $('<div>')
        .data(IS_BOUNDING_BOX, true) // very important, this element needs to be removed later
        .css({
          position: 'absolute',
          left: minLeft,
          top: minTop,
          width: maxRight - minLeft,
          height: maxBottom - minTop
        })
        .appendTo($container);

      return $bb;
    }

    api = {
      start: function (startIdx, single) {
        if (isActive) {
          api.stop();
          return;
        }
        for (var i = 0; i < 4; i++) {
          overlays.push($('<div class="lab-help-overlay lab-help-next"></div>').appendTo($container));
        }
        $tip = $('<div class="lab-help-tip lab-help-next" tabindex="-1"></div>').appendTo($container);
        if (single) {
          $instructions = $('<div class="lab-help-instructions">' +
                            '<span class="lab-help-next">Click overlay to hide help tip</span>' +
                            '</div>').appendTo($container);
          $container.on("click.lab-help-next", ".lab-help-next", api.stop);
        } else {
          $instructions = $('<div class="lab-help-instructions">' +
                            '<span class="lab-help-prev btn"><</span>' +
                            '<span class="lab-help-next">Click overlay to see next help tip</span>' +
                            '<span class="lab-help-next btn">></span>' +
                            '</div>').appendTo($container);
          $container.on("click.lab-help-next", ".lab-help-next", api.next);
          $container.on("click.lab-help-prev", ".lab-help-prev", api.prev);
          $tip.on('keydown.lab-help', function(event) {
            switch(event.keycode || event.which) {
              case 37: // left-arrow
                api.prev();
                break;
              case 39: // right-arrow
                api.next();
                break;
            }
            event.preventDefault();
            event.stopPropagation();
          });
        }
        isActive = true;
        tipIdx = startIdx != null ? startIdx : 0;
        if (single) {
          showTip();
        } else {
          // .next() implements logic related to showcase mode.
          tipIdx--;
          api.next();
        }
        dispatch.start();
      },

      showSingle: function (componentName) {
        for(var i = 0; i < helpTips.length; i++) {
          var comp = helpTips[i].component;
          // Note that comp can be a string or array.
          if (typeof comp === "string" && comp === componentName || comp.indexOf(componentName) !== -1) {
            api.start(i, true);
            return;
          }
        }
      },

      stop: function () {
        $tip.remove();
        $instructions.remove();
        overlays.forEach(function ($overlay) {
          $overlay.remove();
        });
        overlays.length = 0;
        $container.off("click.lab-help-next", ".lab-help-next");
        $container.off("click.lab-help-prev", ".lab-help-prev");
        isActive = false;
        dispatch.stop();
      },

      next: function () {
        tipIdx++;
        // Skip help tips that have showcase property set to false.
        while(tipIdx < helpTips.length && !helpTips[tipIdx].showcase) {
          tipIdx++;
        }
        if (tipIdx >= helpTips.length) {
          api.stop();
          return;
        }
        showTip();
      },

      prev: function () {
        tipIdx--;
        // Skip help tips that have showcase property set to false.
        while(!tipIdx >= 0 && helpTips[tipIdx].showcase) {
          tipIdx--;
        }
        if (tipIdx < 0) {
          api.stop();
          return;
        }
        showTip();
      },

      isActive: function () {
        return isActive;
      },

      hasShowcase: function () {
        return helpTips.filter(function(h) { return h.showcase }).length > 0;
      },

      enableLogging: function(logFunc) {
        var startTime = null;
        dispatch.on('.logging', null);
        dispatch.on('start.logging', function () {
          logFunc('HelpTipsOpened');
          startTime = Date.now();
        });
        dispatch.on('stop.logging', function () {
          logFunc('HelpTipsClosed', {wasOpenFor: (Date.now() - startTime) / 1000});
        });
      }
    };

    dispatch.mixInto(api);

    return api;
  };
});

/*global define: false, $: false */

/**
 * Lab-compatible tooltips based on jQuery-UI tooltips. The custom styling is used and tooltips
 * scale themselves according to the font-size of parent div.
 *
 * There is also a special algorithm for delaying tooltips. When you hover over element with
 * tooltip, it will be shown after 2 seconds. Then if you move mouse pointer fast to another
 * tooltip-able element, tooltip will be shown much faster. This helps user read all tooltips
 * quickly in case of need.
 *
 * Implementation details:
 *
 * There are a few icky solutions. First of all we have to manually set font-size of tooltip
 * container, based on #responsive content font-size, as we can't append tooltip to this div.
 * What's more, there is no way to set font-size before positioning, so we have to position
 * tooltip again after updating font-size (so its size too).
 *
 * Also algorithm for dynamical tooltips delay requires a lot of customization. jQuery-UI
 * implementation doesn't support behavior we expect. We hide tooltip manually in 'open' callback
 * and set interval which shows it again after calculated amount of milliseconds. Weird, but works
 * quite fine.
 */
define('common/views/tooltip',['require','common/benchmark/benchmark'],function (require) {

  var benchmark = require("common/benchmark/benchmark"),
      lastClose = 0,

      customTooltipsEnabled = (function () {
        // Disable custom tooltips on mobile devices, as e.g. on iPad they cause that
        // user have to tap each component twice as first tap only opens a tooltip.
        if (benchmark.isMobile) return false;
        // Disable tooltips in all Safari version older than 7. They are causing weird issues there.
        // See the related PT story and more precise problem description:
        // https://www.pivotaltracker.com/s/projects/442903/stories/59910282
        var browser = benchmark.browser.browser;
        // parseInt() will convert e.g. "7.0/537.71" just to 7.
        var version = parseInt(benchmark.browser.version, 10);
        if (browser === "Safari" && version < 7) return false;
        // Tooltips are enabled by default.
        return true;
      }());

  function tooltip($parent) {
    if (!customTooltipsEnabled) return;

    var $tooltip = null,
        fadeInID = null,
        fadeOutID = null,
        wasShown = false;

    function position(target) {
      // Update font-size using $parent div font-size.
      // Lab Interactives scaling is based on the font-size of this div.
      var fontSize = $parent.css("font-size"),
          vertOffset = + parseFloat(fontSize) * 0.35,
          // workaround jQueryUI tooltip issue; it removes title attribute on focus event
          $posTarget = $(target).closest("[title], [aria-describedby]");
      $tooltip.css("font-size", fontSize);
      // Font-size of the top container changes also dimensions of various elements
      // that are defined in ems, so calculate correct position for tooltip.
      if (!$tooltip.is(":visible")) {
        // Show invisible tooltip, as positioning can't work with hidden elements.
        $tooltip.show();
      }
      $tooltip.position({
        of: $posTarget,
        collision: "flipfit flipfit",
        within: $parent,
        // Arrow's height depends on font-size (as it's defined in ems).
        my: "center top+" + vertOffset,
        at: "center bottom",
        using: function(position, feedback) {
          $(this).css(position);
          // Add arrow for nicer look & feel.
          $("<div>")
            .addClass("ui-tooltip-arrow")
            .addClass(feedback.vertical)
            .addClass(feedback.horizontal)
            .appendTo(this);
        }
      });
    }

    function clearTooltipState() {
      if ($tooltip) {
        $tooltip.hide();
      }
      clearInterval(fadeInID);
      clearInterval(fadeOutID);
      wasShown = false;
      $tooltip = null;
    }

    $parent.tooltip({
      show: false,
      hide: false,
      open: function (event, ui) {
        var delayVal = 3 * Math.min(500, Date.now() - lastClose);

        // Ensure that only one tooltip is visible and tracked by $tooltip and the fadein/fadeout
        // timeres at one time. (A focus event can cause a tooltip to be opened on the previously
        // hovered element just before a tooltip is opened on the currently hovered element, without
        // a close event in between.)
        if ($tooltip !== null) {
          clearTooltipState();
        }

        $tooltip = ui.tooltip;
        position(event.originalEvent.target);
        // Custom delayed animation. Delay value is based on the last user actions.
        $tooltip.hide();
        fadeInID = setTimeout(function () {
          $tooltip.fadeIn();
          wasShown = true;
        }, delayVal);
        fadeOutID = setTimeout(function () {
          $tooltip.fadeOut();
        }, delayVal + 5000);
      },
      close: function (event, ui) {
        if (!$tooltip || ui.tooltip[0] !== $tooltip[0]) {
          return;
        }

        if (wasShown) {
          lastClose = Date.now();
        }
        clearTooltipState();
      }
    });
  }

  return tooltip;
});

/*global define, unescape, escape */

/**
 * Cookies helper adapted from MDN pages. Original docs:
 * https://developer.mozilla.org/en-US/docs/DOM/document.cookie
 */
define('common/cookies',[],function () {

  return {
    getItem: function (sKey) {
      return unescape(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
    },
    setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
      if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
      var sExpires = "";
      if (vEnd) {
        switch (vEnd.constructor) {
          case Number:
            sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
            break;
          case String:
            sExpires = "; expires=" + vEnd;
            break;
          case Date:
            sExpires = "; expires=" + vEnd.toGMTString();
            break;
        }
      }
      document.cookie = escape(sKey) + "=" + escape(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
      return true;
    },
    removeItem: function (sKey, sPath) {
      if (!sKey || !this.hasItem(sKey)) { return false; }
      document.cookie = escape(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sPath ? "; path=" + sPath : "");
      return true;
    },
    hasItem: function (sKey) {
      return (new RegExp("(?:^|;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
    },
    keys: /* optional method: you can safely remove it! */ function () {
      var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
      for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = unescape(aKeys[nIdx]); }
      return aKeys;
    }
  };
});

/*!
* screenfull
* v3.0.2 - 2017-03-13
* (c) Sindre Sorhus; MIT License
*/
(function () {
	'use strict';

	var isCommonjs = typeof module !== 'undefined' && module.exports;
	var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;

	var fn = (function () {
		var val;

		var fnMap = [
			[
				'requestFullscreen',
				'exitFullscreen',
				'fullscreenElement',
				'fullscreenEnabled',
				'fullscreenchange',
				'fullscreenerror'
			],
			// new WebKit
			[
				'webkitRequestFullscreen',
				'webkitExitFullscreen',
				'webkitFullscreenElement',
				'webkitFullscreenEnabled',
				'webkitfullscreenchange',
				'webkitfullscreenerror'

			],
			// old WebKit (Safari 5.1)
			[
				'webkitRequestFullScreen',
				'webkitCancelFullScreen',
				'webkitCurrentFullScreenElement',
				'webkitCancelFullScreen',
				'webkitfullscreenchange',
				'webkitfullscreenerror'

			],
			[
				'mozRequestFullScreen',
				'mozCancelFullScreen',
				'mozFullScreenElement',
				'mozFullScreenEnabled',
				'mozfullscreenchange',
				'mozfullscreenerror'
			],
			[
				'msRequestFullscreen',
				'msExitFullscreen',
				'msFullscreenElement',
				'msFullscreenEnabled',
				'MSFullscreenChange',
				'MSFullscreenError'
			]
		];

		var i = 0;
		var l = fnMap.length;
		var ret = {};

		for (; i < l; i++) {
			val = fnMap[i];
			if (val && val[1] in document) {
				for (i = 0; i < val.length; i++) {
					ret[fnMap[0][i]] = val[i];
				}
				return ret;
			}
		}

		return false;
	})();

	var screenfull = {
		request: function (elem) {
			var request = fn.requestFullscreen;

			elem = elem || document.documentElement;

			// Work around Safari 5.1 bug: reports support for
			// keyboard in fullscreen even though it doesn't.
			// Browser sniffing, since the alternative with
			// setTimeout is even worse.
			if (/5\.1[.\d]* Safari/.test(navigator.userAgent)) {
				elem[request]();
			} else {
				elem[request](keyboardAllowed && Element.ALLOW_KEYBOARD_INPUT);
			}
		},
		exit: function () {
			document[fn.exitFullscreen]();
		},
		toggle: function (elem) {
			if (this.isFullscreen) {
				this.exit();
			} else {
				this.request(elem);
			}
		},
		onchange: function (callback) {
			document.addEventListener(fn.fullscreenchange, callback, false);
		},
		onerror: function (callback) {
			document.addEventListener(fn.fullscreenerror, callback, false);
		},
		raw: fn
	};

	if (!fn) {
		if (isCommonjs) {
			module.exports = false;
		} else {
			window.screenfull = false;
		}

		return;
	}

	Object.defineProperties(screenfull, {
		isFullscreen: {
			get: function () {
				return Boolean(document[fn.fullscreenElement]);
			}
		},
		element: {
			enumerable: true,
			get: function () {
				return document[fn.fullscreenElement];
			}
		},
		enabled: {
			enumerable: true,
			get: function () {
				// Coerce to boolean in case of old WebKit
				return Boolean(document[fn.fullscreenEnabled]);
			}
		}
	});

	if (isCommonjs) {
		module.exports = screenfull;
	} else {
		window.screenfull = screenfull;
	}
})();

define("screenfull", (function (global) {
    return function () {
        var ret, fn;
        return ret || global.screenfull;
    };
}(this)));

/*global define, $ */

define('common/controllers/setup-banner',['lab.config','common/controllers/text-controller','common/controllers/image-controller','common/controllers/div-controller','common/controllers/playback-controller','screenfull'],function () {

  var labConfig          = require('lab.config'),
      TextController     = require('common/controllers/text-controller'),
      ImageController    = require('common/controllers/image-controller'),
      DivController      = require('common/controllers/div-controller'),
      PlaybackController = require('common/controllers/playback-controller'),
      screenfull         = require('screenfull'),

      topBarHeight    = 1.5,
      topBarFontScale = topBarHeight * 0.65,
      topBarVerticalPadding = topBarHeight / 10;

  /**
   * Returns a hash containing:
   *  - components,
   *  - containers,
   *  - layout definition (components location).
   * All these things are used to build the interactive banner.
   *
   * @param {InteractivesController} controller
   * @param {Object} interactive Interactive JSON definition.
   * @param {CreditsDialog} creditsDialog
   * @param {AboutDialog} aboutDialog
   * @param {ShareDialog} shareDialog
   */
  return function setupBanner(controller, interactive, creditsDialog, aboutDialog, shareDialog) {
    var components = {},
        template = [],
        layout = {},
        i18n = controller.i18n,
        body, requestFullscreenMethod;

    function createElementInContainer(element, container) {
      var Controller;

      if (element.type === "text") {
        Controller = TextController;
      } else if (element.type === "image") {
        Controller = ImageController;
      } else if (element.type === "div") {
        Controller = DivController;
      } else if (element.type === "playback") {
        Controller = PlaybackController;
      }

      components[element.id] = new Controller(element, controller);
      template.push(container);
      layout[container.id] = [element.id];
    }

    // Checks if there is a "playback" component in interactive JSON component section.
    function isPlaybackDefinedByAuthor() {
      for (var i = 0; i < interactive.components.length; i++) {
        if (interactive.components[i].type === "playback") return true;
      }
      return false;
    }

    function setupTopBar() {
      template.push({
        "id": "top-bar",
        "top": "0",
        "left": "0",
        "height": topBarHeight + "em",
        "padding-top": topBarVerticalPadding + "em",
        "padding-bottom": topBarVerticalPadding + "em",
        "width": "container.width",
        "aboveOthers": true
      });

      if (interactive.i18nMetadata) {
        createElementInContainer({
          "type": "div",
          "id": "lang-icon",
          "width": "1.8em",
          "height": "1.35em",
          "tooltip": i18n.t("banner.lang_tooltip")
        },
        {
          "id": "banner-lang",
          "top": "0",
          "height": topBarHeight + "em",
          "right": "container.right",
          "padding-top": topBarVerticalPadding + "em",
          "padding-bottom": topBarVerticalPadding + "em",
          "padding-left": "0.75em",
          "padding-right": "0.25em",
          "aboveOthers": true
        });
      }

      createElementInContainer({
        "type": "text",
        "id": "about-link",
        "text": i18n.t("banner.about"),
        "onClick": function () {
          aboutDialog.open();
        },
        "tooltip": i18n.t("banner.about_tooltip")
      },
      {
        "id": "banner-right",
        "fontScale": topBarFontScale,
        "top": "0",
        "height": topBarHeight + "em",
        "padding-top": topBarVerticalPadding + "em",
        "padding-bottom": topBarVerticalPadding + "em",
        "right": interactive.i18nMetadata ? "banner-lang.left" : "interactive.right",
        "padding-left": "1em",
        "padding-right": "0.75em",
        "align": "right",
        "aboveOthers": true
      });

      // Define sharing link only if sharing is enabled.
      // Note that due to layout limitations, banner-middle container
      // has to be defined *after* banner-right container which is used
      // in its specification!
      if (labConfig.sharing) {
        createElementInContainer({
          "type": "text",
          "id": "share-link",
          "text": controller.i18n.t("banner.share"),
          "onClick": function () {
            shareDialog.open();
          },
          "tooltip": i18n.t("banner.share_tooltip")
        },
        {
          "id": "banner-middle",
          "fontScale": topBarFontScale,
          "top": "0",
          "height": topBarHeight + "em",
          "padding-top": topBarVerticalPadding + "em",
          "padding-bottom": topBarVerticalPadding + "em",
          "right": "banner-right.left",
          "padding-right": "1em",
          "align": "right",
          "aboveOthers": true
        });
      }

      createElementInContainer({
        "type": "div",
        "id": "interactive-reload-icon",
        "content": '<i class="icon-repeat"></i>',
        "onClick": function () {
          controller.reloadInteractive();
        },
        "tooltip": i18n.t("banner.reload_tooltip")
      },
      {
        "id": "banner-reload",
        "fontScale": topBarFontScale,
        "top": "0",
        "height": topBarHeight + "em",
        "padding-top": topBarVerticalPadding + "em",
        "padding-bottom": topBarVerticalPadding + "em",
        "left": "0.7em",
        "padding-right": "1em",
        "align": "left",
        "aboveOthers": true
      });

      if (controller.helpSystem && controller.helpSystem.hasShowcase()) {
        createElementInContainer({
          "type": "div",
          "id": "main-help-icon",
          "content": '<i class="icon-question-sign lab-help-icon"></i>',
          "onClick": function () {
            if (!controller.helpSystem.isActive()) {
              controller.helpSystem.start();
            } else {
              controller.helpSystem.stop();
            }
          },
          "tooltip": i18n.t("banner.help_tooltip")
        },
        {
          "id": "banner-help",
          "fontScale": topBarFontScale,
          "top": "0",
          "height": topBarHeight + "em",
          "padding-top": topBarVerticalPadding + "em",
          "padding-bottom": topBarVerticalPadding + "em",
          // "banner-right" can be undefined, so check it.
          "left": "banner-reload.right",
          "padding-right": "1em",
          "align": "left",
          "aboveOthers": true
        });

        // Note that help system has to be initialized before we setup banner!
        controller.helpSystem.on("start.icon", function () {
          var $icon = $("#main-help-icon .lab-help-icon");
          $icon.addClass("icon-remove-sign active");
          $icon.removeClass("icon-question-sign");
        });
        controller.helpSystem.on("stop.icon", function () {
          var $icon = $("#main-help-icon .lab-help-icon");
          $icon.addClass("icon-question-sign");
          $icon.removeClass("icon-remove-sign active");
        });
      }
    }

    function setupBottomBar() {
      template.push({
        "id": "bottom-bar",
        "bottom": "container.height",
        "left": "0",
        "width": "container.width",
        "height": "2.5em",
        "belowOthers": true
      });

      createElementInContainer({
        "type": "div",
        "id": "credits-link",
        "height": "2.5em",
        "width": "8.1em",
        "classes": ["credits"],
        "tooltip": i18n.t("banner.credits_tooltip"),
        "onClick": function () {
          creditsDialog.open();
        }
      },
      {
        "id": "banner-bottom-left",
        "bottom": "container.height",
        "left": "0",
        "padding-left": "0.3em",
        "align": "left",
        "belowOthers": true
      });

      if (screenfull.enabled) {
        // Note: This requires iframe to be embedded with 'allowfullscreen=true'.
        createElementInContainer({
          "type": "div",
          "id": "fullsize-link",
          "height": "2.5em",
          "width": "2.5em",
          "classes": ["fullscreen"],
          "tooltip": i18n.t("banner.fullscreen_tooltip"),
          "onClick": function () {
            if (!screenfull.isFullscreen) {
              screenfull.request(document.body);
              controller.logAction('FullScreenStarted');
            } else {
              screenfull.exit();
            }
          }
        },
        {
          "id": "banner-bottom-right",
          "bottom": "container.height",
          "right": "container.width",
          "align": "left",
          "padding-left": "1em",
          "belowOthers": true
        });
      }

      if (!isPlaybackDefinedByAuthor()) {
        // Define playback component automatically only if it hasn't been done by interactive author.
        createElementInContainer({
          "type": "playback",
          "id": "playback"
        },
        {
          "id": "interactive-playback-container",
          "bottom": "container.height",
          "height": "banner-bottom-left.height",
          "left": "banner-bottom-left.right",
          // note that banner-bottom-right may not be defined
          "right": template.map(function (o) { return o.id; }).indexOf('banner-bottom-right') >= 0 ?
            "banner-bottom-right.left" : "container.right",
          "belowOthers": true
        });
      }
    }

    if (interactive.showTopBar) {
      setupTopBar();
    }

    if (interactive.showBottomBar) {
      setupBottomBar();
    }

    return {
      components: components,
      template: template,
      layout: layout
    };
  };
});

/*global define, $ */
define('common/controllers/about-dialog',['require','common/markdown-to-html','common/inherit','common/controllers/basic-dialog'],function (require) {

  var markdownToHTML = require('common/markdown-to-html'),
      inherit        = require('common/inherit'),
      BasicDialog    = require('common/controllers/basic-dialog');

  /**
   * About Dialog. Inherits from Basic Dialog.
   *
   * @constructor
   */
  function AboutDialog(parentSelector, i18n, interactive) {
    BasicDialog.call(this, {dialogClass: "about-dialog", appendTo: parentSelector}, i18n);
    this._i18n = i18n;
    if (interactive) {
      this.update(interactive)
    }
  }
  inherit(AboutDialog, BasicDialog);

  /**
   * Updates dialog content using interactive JSON definition.
   *
   * @param {Object} interactive Interactive JSON definition.
   */
  AboutDialog.prototype.update = function(interactive) {
    var $aboutContent = $("<div>");

    this.set("title", this._i18n.t("about_dialog.title", {interactive_title: interactive.title}));

    if (interactive.subtitle) {
      // Bold the subtitle using markdown (**).
      $aboutContent.append(markdownToHTML("**" + interactive.subtitle + "**"));
    }
    $aboutContent.append(markdownToHTML(interactive.about));

    this.setContent($aboutContent);
  };

  return AboutDialog;
});

/*global define */
define('common/controllers/copyright',[],function () {
  return function getCopyright(i18n) {
    var CC_link = '<a class="opens-in-new-window" href="http://concord.org" target="_blank">The Concord Consortium</a>';
    var MIT_link = '<a class="opens-in-new-window" href="http://opensource.org/licenses/MIT" target="_blank">MIT</a>';
    var license_link = '<a class="opens-in-new-window" href="https://github.com/concord-consortium/lab/blob/master/license.md" target="_blank">license.md</a>';
    var concord_org_link = '<a class="opens-in-new-window" href="http://concord.org/" target="_blank">http://concord.org</a>';
    return '<div class="copyright-section">' +
           '<strong>' + i18n.t('copyright.copyright') + ' © ' + new Date().getFullYear() + '</strong> ' + CC_link + '. ' +
           i18n.t('copyright.all_rights_reserved') + ' ' +
           i18n.t('copyright.license', {
             MIT_link: MIT_link,
             license_link: license_link
           }) + ' ' +
           i18n.t('copyright.attribution', {
            concord_org_link: concord_org_link
           }) +
           '</div>';
  };
});


define('text!common/controllers/share-dialog.tpl',[],function () { return '<div>\n  <h2>\n    {{{paste_email_im}}}\n  </h2>\n  <textarea>{{embeddableSharingUrl}}</textarea>\n  <h2>{{paste_html}}</h2>\n  <p>{{select_size}}\n    <select id=\'iframe-size\'>\n      <option value=\'smaller\'>{{size_smaller}}</option>\n      <option selected value=\'actual\'>{{size_actual}}</option>\n      <option value=\'larger\'>{{size_larger}}</option>\n    </select>\n  </p>\n  <textarea id=\'share-iframe-content\'></textarea>\n  {{{copyright}}}\n</div>\n';});

/*global define, $ */
define('common/controllers/share-dialog',['require','lab.config','mustache','common/inherit','common/controllers/basic-dialog','common/controllers/copyright','text!common/controllers/share-dialog.tpl'],function (require) {

  var labConfig      = require('lab.config'),
      mustache       = require('mustache'),
      inherit        = require('common/inherit'),
      BasicDialog    = require('common/controllers/basic-dialog'),
      getCopyright   = require('common/controllers/copyright'),
      shareDialogTpl = require('text!common/controllers/share-dialog.tpl'),

      location = document.location,

      // A tiny template, so define it inline and compile immediately.
      iframeTpl = mustache.compile('<iframe width="{{width}}px" height="{{height}}px" ' +
        'frameborder="no" scrolling="no" allowfullscreen="true" webkitallowfullscreen="true"' +
        ' mozallowfullscreen="true" src="{{{embeddableSharingUrl}}}"></iframe>');

  /**
   * Share Dialog. Inherits from Basic Dialog.
   *
   * @constructor
   */
  function ShareDialog(parentSelector, interactiveContainerSelector, i18n, interactive) {
    var hash           = location.hash,
        origin         = location.href.match(/(.*?\/\/.*?)\//)[1],
        embeddablePath = location.pathname;

    BasicDialog.call(this, {dialogClass: "share-dialog", appendTo: parentSelector}, i18n);

    /** @private */
    this._view = {
      paste_html: i18n.t("share_dialog.paste_html"),
      select_size: i18n.t("share_dialog.select_size"),
      size_larger: i18n.t("share_dialog.size_larger", {val: 50}),
      size_actual: i18n.t("share_dialog.size_actual"),
      size_smaller: i18n.t("share_dialog.size_smaller", {val: 30}),
      copyright: getCopyright(i18n)
    };
    this._i18n = i18n;

    if (labConfig.homeForSharing) {
      this._view.embeddableSharingUrl = labConfig.homeForSharing + labConfig.homeEmbeddablePath + hash;
    } else {
      this._view.embeddableSharingUrl = origin + embeddablePath + hash;
    }

    var link = "<a class='opens-in-new-window' href='" + this._view.embeddableSharingUrl +
               "' target='_blank'>" + i18n.t("share_dialog.link") + "</a>";
    this._view.paste_email_im = i18n.t("share_dialog.paste_email_im", {link: link});

    this.setContent(mustache.render(shareDialogTpl, this._view));

    /** @private */
    this._$interactiveContainer = $(interactiveContainerSelector);
    /** @private */
    this._$iframeSize = this.$element.find("#iframe-size");
    /** @private */
    this._$iframeContent = this.$element.find("#share-iframe-content");

    this._$iframeSize.on('change', $.proxy(this.updateIframeSize, this));
    this.updateIframeSize();

    if (interactive) {
      this.update(interactive)
    }
  }
  inherit(ShareDialog, BasicDialog);

  /**
   * Updates size of the Interactive iframe in the share dialog.
   */
  ShareDialog.prototype.updateIframeSize = function () {
    var actualWidth = this._$interactiveContainer.innerWidth(),
        actualHeight = this._$interactiveContainer.innerHeight(),
        sizeChoice = this._$iframeSize.val();

    switch(sizeChoice) {
    case "smaller":
      this._view.width = Math.floor(actualWidth * 0.7);
      this._view.height = Math.floor(actualHeight  * 0.7);
      break;
    case "larger":
      this._view.width = Math.floor(actualWidth * 1.5);
      this._view.height = Math.floor(actualHeight  * 1.5);
      break;
    default:
      this._view.width = actualWidth;
      this._view.height = actualHeight;
      break;
    }

    this._$iframeContent.val(iframeTpl(this._view));
  };

  /**
   * Updates dialog content using interactive JSON definition.
   *
   * @param {Object} interactive Interactive JSON definition.
   */
  ShareDialog.prototype.update = function(interactive) {
    this.set("title", this._i18n.t("share_dialog.title", {interactive_title: interactive.title}));
  };

  return ShareDialog;
});


define('text!common/controllers/credits-dialog.tpl',[],function () { return '<div>\n  <p>\n    {{{credits_text}}}\n  </p>\n  {{#showShareable}}\n  <p>\n    {{{find_shareable}}}\n  </p>\n  {{/showShareable}}\n  {{{copyright}}}\n</div>\n';});

/*global define */
define('common/controllers/credits-dialog',['require','lab.config','mustache','common/markdown-to-html','common/inherit','common/controllers/basic-dialog','common/controllers/copyright','text!common/controllers/credits-dialog.tpl'],function (require) {

  var labConfig        = require('lab.config'),
      mustache         = require('mustache'),
      markdownToHTML   = require('common/markdown-to-html'),
      inherit          = require('common/inherit'),
      BasicDialog      = require('common/controllers/basic-dialog'),
      getCopyright     = require('common/controllers/copyright'),
      creditsDialogTpl = require('text!common/controllers/credits-dialog.tpl'),
      CONCORD_URL = 'http://concord.org',
      NEXT_GEN_URL = 'http://mw.concord.org/nextgen/';

  /**
   * Credits Dialog. Inherits from Basic Dialog.
   *
   * @constructor
   */
  function CreditsDialog(parentSelector, i18n, interactive) {
    BasicDialog.call(this, {dialogClass: "credits-dialog", appendTo: parentSelector}, i18n);
    this._i18n = i18n;
    if (interactive) {
      this.update(interactive)
    }
  }
  inherit(CreditsDialog, BasicDialog);

  /**
   * Updates dialog content using interactive JSON definition.
   *
   * @param {Object} interactive Interactive JSON definition.
   */
  CreditsDialog.prototype.update = function(interactive) {
    var hash           = document.location.hash,
        origin         = document.location.href.match(/(.*?\/\/.*?)\//)[1],
        embeddablePath = document.location.pathname,
        i18n           = this._i18n,
        concordUrl     = CONCORD_URL,
        nextGenUrl     = NEXT_GEN_URL,
        interactiveCreditsUrl,
        utmString;

    this.set("title", this._i18n.t("credits_dialog.title", {interactive_title: interactive.title}));

    if (labConfig.homeForSharing) {
      interactiveCreditsUrl = labConfig.homeForSharing + labConfig.homeEmbeddablePath + hash;
    } else {
      interactiveCreditsUrl = origin + embeddablePath + hash;
    }

    if (labConfig.utmCampaign) {
      utmString = "utm_source=" + encodeURIComponent(interactive.title.replace(/ /gi,"+")) +
        "&utm_medium=embedded_interactive&utm_campaign=" + labConfig.utmCampaign;
      concordUrl += "?" + utmString;
      nextGenUrl += "?" + utmString;
      interactiveCreditsUrl += "?" + encodeURI("utm_source=embed_link&utm_medium=embedded_interactive&utm_campaign=" + labConfig.utmCampaign);
    }

    var view = {
      // Content of the credits dialog. If it's not specified, the default, translatable text will be used.
      credits_text: interactive.credits ? markdownToHTML(interactive.credits) : i18n.t("credits_dialog.credits_text", {
        CC_link: "<a class='opens-in-new-window' href='" + concordUrl + "' target='_blank'>Concord Consortium</a>",
        Next_Gen_MW_link: "<a class='opens-in-new-window' href='" + nextGenUrl + " ' target='_blank'>Next-Generation Molecular Workbench</a>",
        Google_link: "<a class='opens-in-new-window' href='http://www.google.org/' target='_blank'>Google.org</a>"
      }),
      find_shareable: i18n.t("credits_dialog.find_shareable", {
        shareable_ver_link: '<a href="' + interactiveCreditsUrl + '" class="opens-in-new-window" target="_blank">' + i18n.t('credits_dialog.shareable_ver') + '</a>',
        concord_org_link: '<a href="' + concordUrl + '" class="opens-in-new-window" target="_blank">concord.org</a>'
      }),
      copyright: getCopyright(i18n)
    };

    if (!labConfig.sharing) {
      view.showShareable = true;
    }

    this.setContent(mustache.render(creditsDialogTpl, view));
  };

  return CreditsDialog;
});

/*global define: false */
// ------------------------------------------------------------
//
//   Semantic Layout Configuration
//
// ------------------------------------------------------------

define('common/layout/semantic-layout-config',[],function () {
  return {
    /**
      Maximum number of iterations of the layout algorithm during single layoutInteractive() call.
    */
    iterationsLimit: 35,
    /**
      Minimum width of the model.
    */
    minModelWidth: 150,
    /**
      Minimum font size (in ems).
    */
    minFontSize: 0.65,
    /**
      Canoncical font size (in ems).
    */
    canonicalFontSize: 0.9,
    /**
      Canonical width of the interactive, it decides about font size.
      (canoncicalFontSize * fontScale) em is used for the interactive which has such width:
    */
    canonicalWidth: 565,

    /**
      Colors used to mark layout containers in the authoring mode.
    */
    containerColors: [
      "rgba(0,0,255,0.1)", "rgba(255,0,0,0.1)", "rgba(0,255,0,0.1)", "rgba(255,255,0,0.1)",
      "rgba(0,255,255,0.1)", "rgba(255,255,128,0.1)", "rgba(128,255,0,0.1)", "rgba(255,128,0,0.1)"
    ]
  };
});
/*global define: false, $: false */
// ------------------------------------------------------------
//
//   Semantic Layout
//
// ------------------------------------------------------------

define('common/layout/semantic-layout',['require','common/layout/semantic-layout-config','arrays','common/console','common/alert'],function (require) {

  var layoutConfig = require('common/layout/semantic-layout-config'),
      arrays       = require('arrays'),
      console      = require('common/console'),
      alert        = require('common/alert');

  return function SemanticLayout() {
        // Public API.
    var layout,

        // Main container of the interactive. Font scaling will be applied to it.
        $mainContainer,
        // Parent of all containers, it should equal to $mainContainer or one of its children.
        $containersParent,

        // Array of containers specifications.
        containerSpecList,
        // Hash containing content of container for a given container ID.
        containersContent,
        // Hash of component controllers.
        componentByID,
        modelController,
        aspectRatio,
        fontScale,
        // Top, bottom and left padding, but NOT right...
        // It's been implemented like this and at the moment it's impossible to change as it would break many
        // existing interactives.
        padding,
        // Padding multiplied by container scale.
        scaledPadding,

        // Container specifications by ID.
        containerSpecByID,
        // Container jQuery objects by ID. Main container is used to position widgets.
        $containerByID,
        // Inner container is used to set margin and padding.
        $innerContainerByID,
        // Model container jQuery object.
        $modelContainer,

        // Dimensions of the container.
        availableWidth,
        availableHeight,

        // To optimize getHeightForWidth for model containers that care about the font size, kee
        // track of changes
        fontSizeChanged = false,

        // Most important variables.
        // In fact they define state of the layout.
        modelWidth,
        modelTop,
        modelLeft,
        paddingTop,
        paddingLeft,
        paddingBottom;

    function reset() {
      modelWidth = layoutConfig.minModelWidth;
      modelTop = 0;
      modelLeft = 0;
      paddingBottom = 0;
    }

    function getDimensionOfContainer($container, dim) {
      var position = $container.position();

      switch (dim) {
        case "top":
          return position.top;
        case "bottom":
          return position.top + $container.outerHeight();
        case "left":
          return position.left;
        case "right":
          return position.left + $container.outerWidth();
        case "height":
          return $container.outerHeight();
        case "width":
          return $container.outerWidth();
      }
    }

    function setFontSize() {
      var canonicalWidth = layoutConfig.canonicalWidth,
          canonicalHeight = canonicalWidth / aspectRatio,
          containerScale, font;

      containerScale = Math.min($mainContainer.width() / canonicalWidth,
                                $mainContainer.height() / canonicalHeight);

      scaledPadding = containerScale * padding;

      paddingTop = scaledPadding;
      paddingLeft = scaledPadding;
      paddingBottom = scaledPadding;

      font = layoutConfig.canonicalFontSize * fontScale * containerScale;

      // Ensure min font size (in 'em').
      if (font < layoutConfig.minFontSize) {
        font = layoutConfig.minFontSize;
      }

      // Set font-size of interactive container.
      $mainContainer.css("font-size", font + "em");
      fontSizeChanged = true;
    }

    function createContainers() {
      var container, id, prop, i, ii;

      $containerByID = {};
      $innerContainerByID = {};
      containerSpecByID = {};

      for (i = 0, ii = containerSpecList.length; i < ii; i++) {
        container = containerSpecList[i];
        id = container.id;
        containerSpecByID[id] = container;
        $containerByID[id] = $("<div id='" + id + "'>").appendTo($containersParent);
        $containerByID[id].css({
          "display": "inline-block",
          "position": "absolute"
        });
        $innerContainerByID[id] = $('<div class="inner-container">');
        $innerContainerByID[id].appendTo($containerByID[id]);

        if (container.width === undefined) {
          // Disable wrapping of elements in a container, which
          // doesn't define explicit width. It's required to calculate
          // layout correctly.
          $containerByID[id].css("white-space", "nowrap");
        }


        for (prop in container) {
          if (!container.hasOwnProperty(prop)) continue;
          if (/^margin/.test(prop)) {
            // Margin is in fact padding of the outer container.
            $containerByID[id].css(prop.replace("margin", "padding"), container[prop]);
          }
          else if (/^padding/.test(prop)) {
            // Padding is simply padding of the inner container.
            $innerContainerByID[id].css(prop, container[prop]);
          }
          else if (prop === "align") {
            $containerByID[id].css("text-align", container[prop]);
          }
          else if (prop === "min-width") {
            $containerByID[id].css("min-width", container[prop]);
          }
          else if (prop === "fontScale") {
            $containerByID[id].css("font-size", container[prop] + "em");
          }
          else if (prop === "border") {
            $innerContainerByID[id].css("border", container[prop]);
          }
          else if (prop === "background") {
            $innerContainerByID[id].css("background", container[prop]);
          }
        }
      }
    }

    function placeComponentsInContainers() {
      var id, containerID, divContents, items,
          $row, $rows, $containerComponents,
          lastContainer, comps, errMsg,
          i, ii, j, jj, k, kk;

      comps = $.extend({}, componentByID);

      for (containerID in containersContent) {
        if (!containersContent.hasOwnProperty(containerID)) continue;

        if (!$containerByID[containerID]) {
          // Inform an author and skip this container.
          errMsg = "Incorrect layout definition - '" + containerID + "' container does not exist.";
          alert(errMsg);
          continue;
        }

        divContents = containersContent[containerID];

        if (!arrays.isArray(divContents)) {
          // Inform an author and skip this container.
          errMsg = "Incorrect layout definition for '" + containerID + "' container. It should specify " +
                   "an array of components or an array of arrays of components (multiple rows).";
          alert(errMsg);
          continue;
        }

        if (!arrays.isArray(divContents[0])) {
          // Only one row specified. Wrap it into array to process it easier.
          divContents = [divContents];
        }

        for (j = 0, jj = divContents.length; j < jj; j++) {
          items = divContents[j];
          $row = $('<div class="interactive-row"/>');
          // Each row should have width 100% of its parent container.
          $row.css("width", "100%");
          // When there is only one row, ensure that it fills whole container.
          if (jj === 1) {
            $row.css("height", "100%");
          }
          $innerContainerByID[containerID].append($row);
          for (k = 0, kk = items.length; k < kk; k++) {
            id = items[k];
            if (comps[id] === undefined) {
              // Inform an author and skip this definition.
              alert("Incorrect layout definition. Component with ID '" + id + "'' is not defined.");
              continue;
            }
            $row.append(comps[id].getViewContainer());
            delete comps[id];
          }
        }
      }

      // Add any remaining components to "bottom" or last container.
      lastContainer = containerSpecByID.bottom || containerSpecList[containerSpecList.length-1];
      if (lastContainer) {
        $rows = $innerContainerByID[lastContainer.id].children();
        $row = $rows.last();
        if (!$row.length) {
          $row = $('<div class="interactive-row"/>');
          $innerContainerByID[lastContainer.id].append($row);
        }
        for (id in comps) {
          if (!comps.hasOwnProperty(id)) continue;
          $row.append(comps[id].getViewContainer());
        }
      }

      // When there are multiple components in a container, ensure that there
      // is spacing between them.
      // See src/sass/lab/_semantic-layout.sass for .component-spacing class definition.
      for (i = 0, ii = containerSpecList.length; i < ii; i++) {
        // First children() call returns rows, second one components.
        $containerComponents = $innerContainerByID[containerSpecList[i].id].children().children();
        if ($containerComponents.length > 1) {
          $containerComponents.addClass("component-spacing");
        }
      }
    }

    function positionContainers() {
      var container, $container,
          left, top, right, bottom, height, i, ii, id;

      $modelContainer.css({
        width:  modelWidth,
        height: modelController.getHeightForWidth(modelWidth, fontSizeChanged),
        left:   modelLeft,
        top:    modelTop
      });

      fontSizeChanged = false;

      for (i = 0, ii = containerSpecList.length; i<ii; i++) {
        container = containerSpecList[i];
        $container = $containerByID[container.id];

        if (!container.left && !container.right) {
          container.left = "0";
        }
        if (!container.top && !container.bottom) {
          container.top = "0";
        }

        if (container.left) {
          left = parseDimension(container.left);
          $container.css("left", left);
        }
        if (container.top) {
          top = parseDimension(container.top);
          $container.css("top", top);
        }
        if (container.height) {
          $container.css("height", parseDimension(container.height));
        }
        if (container.width) {
          $container.css("width", parseDimension(container.width));
        }
        if (container.right) {
          right = parseDimension(container.right);
          if (container.left) {
            $container.css("width", right - left);
          } else {
            left = right - $container.outerWidth();
            $container.css("left", left);
          }
        }
        if (container.bottom) {
          bottom = parseDimension(container.bottom);
          if (container.top) {
            $container.css("height", bottom - top);
          } else {
            top = bottom - $container.outerHeight();
            $container.css("top", top);
          }
        }

        // Containers with "aboveOthers" property should be treated in a special
        // way. It's a group of absolutely positioned containers, which is always
        // placed above other containers. So, in fact they define paddingTop
        // for other components.
        if (container.aboveOthers) {
          bottom = getDimensionOfContainer($container, "bottom") + scaledPadding;
          if (bottom > paddingTop) {
            paddingTop = bottom;
          }
        }
        if (container.belowOthers) {
          height = getDimensionOfContainer($container, "height") + scaledPadding;
          if (height > paddingBottom) {
            paddingBottom = height;
          }
        }
      }

      // Shift regular containers (aboveOther == false) according to the top boundary.
      for (id in $containerByID) {
        if (!$containerByID.hasOwnProperty(id)) continue;
        if (containerSpecByID[id] && containerSpecByID[id].aboveOthers) continue;
        if (containerSpecByID[id] && containerSpecByID[id].belowOthers) continue;
        $container = $containerByID[id];
        top = getDimensionOfContainer($container, "top");
        $container.css("top", top + paddingTop);
        left = getDimensionOfContainer($container, "left");
        $container.css("left", left + paddingLeft);
      }
    }

    // shrinks the model to fit in the interactive, given the sizes
    // of the other containers around it.
    function resizeModelContainer() {
      var maxX = -Infinity,
          maxY = -Infinity,
          minX = Infinity,
          minY = Infinity,
          id, $container,
          right, bottom, top, left, ratio;

      for (id in $containerByID) {
        if (!$containerByID.hasOwnProperty(id)) continue;
        if (containerSpecByID[id] && containerSpecByID[id].aboveOthers) continue;
        if (containerSpecByID[id] && containerSpecByID[id].belowOthers) continue;
        $container = $containerByID[id];
        right = getDimensionOfContainer($container, "right");
        if (right > maxX) {
          maxX = right;
        }
        bottom = getDimensionOfContainer($container, "bottom");
        if (bottom > maxY) {
          maxY = bottom;
        }
        left = getDimensionOfContainer($container, "left");
        if (left < minX) {
          minX = left;
        }
        top = getDimensionOfContainer($container, "top");
        if (top < minY) {
          minY = top;
        }
      }

      // Stop condition. We assume that layout is good enough when it fits the container +/- 2px.
      if ((maxX <= availableWidth + 2 && maxY <= (availableHeight-paddingBottom + 2)) &&
          (Math.abs(availableWidth - maxX) < 2 || Math.abs((availableHeight-paddingBottom) - maxY) < 2) &&
          (Math.abs(minX - paddingLeft) < 2 && Math.abs(minY - paddingTop) < 2)) {
        return true;
      }

      ratio = Math.min(availableWidth / maxX, (availableHeight-paddingBottom) / maxY);
      if (!isNaN(ratio)) {
        modelWidth = modelWidth * ratio;
      }
      if (modelWidth < layoutConfig.minModelWidth) {
        modelWidth = layoutConfig.minModelWidth;
      }

      modelLeft -= minX - paddingLeft;
      modelTop -= minY - paddingTop;

      return false;
    }

    // parses arithmetic such as "model.height/2"
    function parseDimension(dim) {
      var vars, i, ii, value;

      if (typeof dim === "number" || /^[0-9]+\.?[0-9]*(em)?$/.test(dim)) {
        return dim;
      }

      // find all strings of the form x.y
      vars = dim.match(/[a-zA-Z][a-zA-Z0-9\-]+\.[a-zA-Z]+/g);

      // replace all x.y's with the actual dimension
      for (i=0, ii=vars.length; i<ii; i++) {
        value = getDimension(vars[i]);
        dim = dim.replace(vars[i], value);
      }
      // eval only if we contain no more alphabetic letters
      // dim can contain strings which are just numbers ...
      // or strings with with expressions like this: "839/2 - 117/2"
      if (/^[^a-zA-Z]*$/.test(dim)) {
        return eval(dim);
      } else {
        return 0;
      }
    }

    // Parses a container's dimension, such as "model.height".
    function getDimension(dim) {
      switch(dim) {
        case "container.left":
        case "container.top":
          return 0;
        case "container.width":
        case "container.right":
          return availableWidth;
        case "container.height":
        case "container.bottom":
          return availableHeight;
        case "interactive.left":
          return paddingLeft;
        case "interactive.top":
          return paddingTop;
        case "interactive.width":
        case "interactive.right":
          return availableWidth - paddingLeft;
        case "interactive.height":
        case "interactive.bottom":
          return availableHeight - paddingTop - paddingBottom;
        default:
          dim = dim.split(".");
          return getDimensionOfContainer($containerByID[dim[0]], dim[1]);
      }
    }

    // Public API.
    layout = {
      /**
       * Setups interactive layout. Creates new containers and places components inside them.
       *
       * This method should be called each time when at least one of the following objects is changed:
       *  - layout template,
       *  - component locations,
       *  - components,
       *  - model controller,
       *  - font scale,
       *  - padding.
       *
       * @param {hash} options:
       *   - {jQuery} $mainContainer Top-most container, font scalling will be applied to it.
       *   - {jQuery} $containersParent Element that will be a parent for containers
       *                                (can be equal to main container or one of its children).
       *   - {array}  containers List of layout containers.
       *   - {Object} layout Hash of components locations, e.g. {"bottom": ["button", "textLabel"]}.
       *   - {Object} components Hash of components controllers. Keys are IDs of the components.
       *   - {number} fontScale Aspect ratio, floating point number, typically around 1.3.
       *   - {number} fontScale Font scale, floating point number, typically between 0.5 and 1.5.
       *   - {number} padding Interactive padding, floating point number, defined in px for canonical interactive size.
       */
      initialize: function (options) {
        $mainContainer = options.$mainContainer;
        $containersParent = options.$containersParent;

        containerSpecList = options.containers;
        containersContent = options.layout;
        componentByID = options.components;
        aspectRatio = options.aspectRatio;
        fontScale = options.fontScale;
        padding = options.padding;

        createContainers();
        placeComponentsInContainers();

        // After .initialize() call client code has to call .setupModel().
        modelController = null;

        $mainContainer.addClass("lab-responsive-content");
      },

      /**
       * Setups model controller, as well as model container provided by it.
       * Model Controller should implement getViewVontainer() method.
       * Always call this function after initialize()!
       *
       * @param {ModelController} newModelController Model Controller object.
       */
      setupModel: function (newModelController) {
        modelController = newModelController;

        if ($containerByID.model) {
          if ($containerByID.model === modelController.getViewContainer()) {
            // Do nothing, the valid model container is already inside interactive container.
            return;
          }
          // If there is an old model container, remove it.
          $containerByID.model.remove();
        }

        $modelContainer = modelController.getViewContainer();
        $modelContainer.css({
          "display": "inline-block",
          "position": "absolute",
          "z-index": "0"
        });
        $modelContainer.appendTo($containersParent);
        $containerByID.model = $modelContainer;
      },

      /**
       * Returns true if semantic layout is ready to perform calculations.
       * @return {boolean}
       */
      isReady: function () {
        if (!modelController) return false;
        else return true;
      },

      /**
       * Layouts interactive. Adjusts size of the model container to ensure that all components are inside the
       * interactive container and all available space is used in the best way.
       */
      layoutInteractive: function () {
        var redraws = layoutConfig.iterationsLimit,
            id;

        console.time('[layout] update');

        reset();
        availableWidth  = $mainContainer.width();
        availableHeight = $mainContainer.height();
        modelWidth = availableWidth; // optimization

        // 0. Set font size of the interactive-container based on its size.
        setFontSize();

        // 1. Calculate optimal layout.
        positionContainers();
        while (--redraws > 0 && !resizeModelContainer()) {
          positionContainers();
        }

        // 2. Notify components that their containers have new sizes.
        modelController.resize();
        for (id in componentByID) {
          if (componentByID.hasOwnProperty(id) && componentByID[id].resize !== undefined) {
            componentByID[id].resize();
          }
        }

        console.timeEnd('[layout] update');

        // Return number of iterations (e.g. for benchmarks).
        return layoutConfig.iterationsLimit - redraws;
      }
    };

    return layout;
  };

});
/*global define*/
define('common/layout/templates',[],function () {
  return {
    "model-only": [],
    "simple": [
      {
        "id": "top",
        "bottom": "model.top",
        "width": "interactive.width"
      },
      {
        "id": "right",
        "top": "model.top",
        "left": "model.right",
        "height": "model.height",
        "padding-left": "1em",
        "padding-right": "0.5em"
      },
      {
        "id": "bottom",
        "top": "model.bottom",
        "width": "interactive.width",
        "padding-top": "0.5em"
      }
    ],
    "narrow-right": [
      {
        "id": "top",
        "bottom": "model.top",
        "width": "interactive.width"
      },
      {
        "id": "right",
        "top": "model.top",
        "left": "model.right",
        "height": "model.height",
        "padding-left": "1em",
        "padding-right": "0.5em",
        "width": "model.width / 4",
        "min-width": "6em"
      },
      {
        "id": "bottom",
        "top": "model.bottom",
        "width": "interactive.width",
        "padding-top": "0.5em"
      }
    ],
    "wide-right": [
      {
        "id": "top",
        "bottom": "model.top",
        "width": "interactive.width"
      },
      {
        "id": "right",
        "top": "model.top",
        "left": "model.right",
        "height": "model.height",
        "padding-left": "1em",
        "padding-right": "0.5em",
        "width": "model.width",
        "min-width": "6em"
      },
      {
        "id": "bottom",
        "top": "model.bottom",
        "width": "interactive.width",
        "padding-top": "0.5em"
      }
    ],
    "left-right-bottom": [
      {
        "id": "left",
        "top": "model.top",
        "height": "model.height",
        "right": "model.left",
        "padding-right": "0.5em"
      },
      {
        "id": "right",
        "top": "model.top",
        "height": "model.height",
        "left": "model.right",
        "padding-left": "1em",
        "padding-right": "0.5em"
      },
      {
        "id": "bottom",
        "top": "model.bottom",
        "left": "model.left",
        "width": "model.width"
      }
    ]
  };
});

/*global define, performance, $ */

define('common/performance',[],function () {
  var nowFunc,
      scope,
      enabled = false;

  if (typeof performance !== 'undefined' && typeof performance.now !== 'undefined') {
    nowFunc = $.proxy(performance.now, performance);
  } else {
    nowFunc = $.proxy(Date.now, Date);
  }

  return {
    /**
     * window.performance.now or Date.now when performance.now is not available.
     * @type {Function}
     */
    now: nowFunc,

    collectData: function (v) {
      enabled = v;
      // Reset data each time when data collection is being started.
      if (enabled) scope = {};
    },

    enterScope: function (name) {
      if (enabled) {
        var s = scope[name];
        if (s === undefined) {
          s = scope[name] = {
            lastTime: 0,
            timeSum: 0,
            count: 0
          };
        }
        s.lastTime = nowFunc();
      }
    },

    leaveScope: function (name) {
      if (enabled) {
        var s = scope[name];
        if (s !== undefined) {
          s.timeSum += nowFunc() - s.lastTime;
          s.count += 1;
        }
      }
    },

    getAvgTime: function (name) {
      var s = scope[name];
      if (s !== undefined) {
        return s.timeSum / s.count;
      }
      return 0;
    },

    getAvgFreq: function (name) { // in Hz
      var s = scope[name];
      if (s !== undefined) {
        return s.count * 1000 / s.timeSum;
      }
      return 0;
    }
  };

});

/*global define, d3, alert */

define('common/controllers/model-controller',['require','lab.config','common/performance'],function (require) {

  var labConfig   = require('lab.config'),
      performance = require('common/performance');

  var global = (function() { return this; }());

  function ModelController(modelUrl, modelOptions, interactivesController,
                           Model, ModelContainer, ScriptingAPI, Benchmarks) {
    var controller,
        model,
        benchmarks,
        modelContainer,

        // Used to track cause of model reset, if known; required to be kept in this closure because
        // it doesn't get passed directly to our model.reset handler
        resetCause,

        // event dispatcher
        dispatch = d3.dispatch('modelLoaded', 'modelReset', 'modelSetupComplete');

    function tickStartHandler() {
      // Use seedrandom library (see vendor/seedrandom) that substitutes an explicitly seeded
      // RC4-based algorithm for Math.random(). When interactive random seed is constant, it ensures
      // that simulations will look the same for different users even if physics engine uses random
      // values. Note that we set seed each tick to be sure that it's always the same before
      // performing simulation step. Otherwise, it's possible that simulations won't be repeatable
      // if e.g. user uses tick history or triggers any code path that calls Math.random()
      // between ticks.
      // Note that event if .randomSeed is based on entropy (so, it's almost random), this still
      // ensures that e.g. when users steps back in tick history and clicks play, he will see
      // the same results like for the first time.
      Math.seedrandom(interactivesController.randomSeed + model.get("time"));
    }

    function tickHandler() {
      performance.enterScope("js-rendering");
      controller.modelContainer.update();
      performance.leaveScope("js-rendering");
    }

    // ------------------------------------------------------------
    //
    //   Model Setup
    // ------------------------------------------------------------
    function setupModel() {
      model = new Model(modelOptions, {
        waitForSetup: true,
        i18n: interactivesController.i18n
      });
      model.on('tickStart.modelController', tickStartHandler);
      model.on('tick.modelController', tickHandler);
      model.on('reset.modelController', resetHandler);
    }

    function resetHandler() {
      // Just use the generic cause, "reset", if no more specific cause of the model reset is
      // available.
      resetCause = resetCause || ModelController.RESET_CAUSE.RESET;
      modelContainer.repaint();
      dispatch.modelReset(resetCause);
    }

    /**
      Note: newModelConfig, newinteractiveViewConfig are optional. Calling this without
      arguments will simply reload the current model.
    */
    // REF TODO rename to load
    function reload(newModelUrl, newModelOptions, suppressEvents) {
      // Since we won't call model.reset() (instead, we will discard the model) we need to make sure
      // that the model knows to dispatch a willReset event.
      if (model && model.willReset) {
        model.willReset();
      }

      modelUrl = newModelUrl || modelUrl;
      modelOptions = newModelOptions || modelOptions;
      setupModel();

      if (modelContainer) {
        modelContainer.bindModel(model, modelUrl);
      } else {
        modelContainer = new ModelContainer(model, controller.modelUrl, interactivesController.i18n);
      }

      if (!suppressEvents) {
        dispatch.modelLoaded(ModelController.LOAD_CAUSE.RELOAD);
      }

    }

    // ------------------------------------------------------------
    //
    // Public methods
    //
    // ------------------------------------------------------------

    controller = {

      get type() {
        return Model.type;
      },
      get benchmarks() {
        return benchmarks;
      },
      get modelUrl() {
        return modelUrl;
      },
      get model() {
        return model;
      },
      get modelContainer() {
        return modelContainer;
      },

      on: function(type, listener) {
        dispatch.on(type, listener);
      },

      getViewContainer: function () {
        return controller.modelContainer.$el;
      },

      getHeightForWidth: function (width, fontSizeChanged) {
        return controller.modelContainer.getHeightForWidth(width, fontSizeChanged);
      },

      /**
        Initializes the model-type-specific renderer within the model container and asks it to
        render.

        The model will not be rendered to the screen until this method is called. For their part,
        renderers should ignore render() and repaint() calls before they have been setup by calling
        this method.

        Call this when the  when the model is ready to be rendered (ie the container has been laid
        out and resized) and again after a new model has been bound to the container and
        has been initialized to the point that it is ready to render.
      */
      initializeView: function() {
        controller.modelContainer.setup();
        controller.modelContainer.repaint();
      },

      resize: function () {
        controller.modelContainer.resize();
      },

      repaint: function () {
        controller.modelContainer.repaint();
      },

      updateView: function() {
        controller.modelContainer.update();
      },

      reload: reload,

      reset: function (cause) {
        model.stop();
        // use the resetCause closure var to make the cause (which the model doesn't know about)
        // available to resetHandler()
        resetCause = cause;
        model.reset();
        resetCause = undefined;
      },

      state: function() {
        return model.serialize();
      },

      ScriptingAPI: ScriptingAPI,

      enableKeyboardHandlers: function () {
        return model.get("enableKeyboardHandlers");
      }
    };

    // ------------------------------------------------------------
    //
    // Initial setup of this modelController:
    //
    // ------------------------------------------------------------

    // REF TODO ugly
    if (modelOptions != null) {
      setupModel();
    }

    // publish model so it can be inspected at console
    global.getModel = function() {
      return model;
    };

    benchmarks = typeof Benchmarks === "function" ? new Benchmarks(controller) : [];
    return controller;
  }

  ModelController.LOAD_CAUSE = {
    RELOAD: 'reload',
    INITIAL_LOAD: 'initialLoad'
  };

  ModelController.RESET_CAUSE = {
    RESET: 'reset'
  };

  return ModelController;
});

/*global define: false, $ */

define('common/serialize',['require','arrays'],function(require) {

  var arrays = require('arrays'),

      infinityToString = function (obj) {
        var i, len;
        if (arrays.isArray(obj)) {
          for (i = 0, len = obj.length; i < len; i++) {
            if (obj[i] === Infinity || obj[i] === -Infinity) {
              obj[i] = obj[i].toString();
            }
          }
        } else {
          for (i in obj) {
            if (obj.hasOwnProperty(i)) {
              if (obj[i] === Infinity || obj[i] === -Infinity) {
                obj[i] = obj[i].toString();
              }
              if (typeof obj[i] === 'object' || arrays.isArray(obj[i])) {
                infinityToString(obj[i]);
              }
            }
          }
        }
      };

  return function serialize(metaData, propertiesHash, count) {
    var result = {}, propName, prop;
    for (propName in metaData) {
      if (metaData.hasOwnProperty(propName)) {
        if (propertiesHash[propName] !== undefined && metaData[propName].serialize !== false) {
          prop = propertiesHash[propName];
          if (arrays.isArray(prop)) {
            result[propName] = arrays.copy(prop, [], count);
          }
          else if (typeof prop === 'object' && prop !== null) {
            result[propName] = $.extend(true, {}, prop);
          }
          else {
            result[propName] = prop;
          }
        }
      }
    }
    // JSON doesn't allow Infinity values so convert them to strings.
    infinityToString(result);
    // TODO: to make serialization faster, replace arrays.copy(prop, [])
    // with arrays.clone(prop) to use typed arrays whenever they are available.
    // Also, do not call "infinityToString" function. This can be useful when
    // we decide to use serialization in tick history manager.
    // Then we can provide toString() function which will use regular arrays,
    // replace each Infinity value with string and finally call JSON.stringify().
    return result;
  };

});

/*global define: false, d3: false */
/**

  This module provides support which Lab model types can use to implement observable properties that
  have custom getters, setters, and validation. It is specialized for the needs of interactive,
  computationally intensive simulations which want to enable UI bindings to simulation-state
  variables that evolve in time and change at potentially every clock tick.

  For example, if the model object using PropertySupport is 'model':

    > model.addObserver('kineticEnergy', function() { console.log(model.properties.kineticEnergy); })
    > model.start()
    3.10225948103683
    3.102259509874652
    3.1022595094558194
    ...
    > model.addObserver('property', function() { console.log('property changed!'); })
    > model.properties.property = 1
    property changed!
    > model.properties.property
    1

  Or, using the "legacy" interface:

    > model.addObserver('kineticEnergy', function() { console.log(model.get('kineticEnergy'); })
    > model.start()
    3.10225948103683
    3.102259509874652
    3.1022595094558194
    ...
    > model.addObserver('property', function() { console.log('property changed!'); })
    > model.set('property', 1)
    property changed!
    > model.get('property')
    1

  The design of this module differs in several ways than the property support implemented by general
  web MVC frameworks such as Backbone, Ember, and Angular.

  First, we assume that the properties module is used to enable UI binding and state saving for a
  simulation engine which has its own internal data structures and which executes many iterations of
  its inner loop between each screen refresh. As a result, we must assume that any computed property
  can change between "clock ticks" and that most computed properties are not simple functions of
  the value of other properties. Therefore we provide mechanisms that must be explicitly invoked by
  the model to synchronize the engine's internal state to the exposed property values when the
  engine considers it appropriate to do so.

  Second, we assume that the most properties are numbers that represent physical quantities that
  either parameterize the simulation or are computed by it.

  Third, we assume that, the simulation may need to save and restore the values of a subset of
  properties outside the usual setter/getter cycle. Specifically, we allow the simulation to define
  two subsets of properties: one that represents the entire set of properties required to restore
  the state of the model, for use when saving the model to storage; and a smaller subset of
  properties that represent the time-varying state of the model, for use when rewinding or fast-
  forwarding the model while it retains the remainder of its state in memory.

*/
define('common/property-support',[],function() {

  // If at all possible, avoid adding dependencies to this module.

  // These are the properties that can be passed as the 'descriptor' argument to defineProperty.
  var descriptorProperties = {

    /**
      A getter function that will be executed whenever the value of this property is read.

      Use this, for example, to make a property reflect internal state of the simulation.

      The property will be considered a "computed property" if and only if it has a getter. The
      return value of the getter is considered the "raw" property value and will be passed through
      the afterGetTransform, if one is defined, to generate the final value of the property.

      The (untransformed) raw getter value will be cached unless the enableCaching property of the
      propertySupport object is false. The cache can be cleared by calling the
      deleteComputedPropertyCachedValues method of the propertySupport object. The caching normally
      occurs lazily, but paired calls to the storeComputedProperties and
      notifyChangedComputedProperties methods of the propertySupport object cause all properties
      with getters to be computed and then recomputed, triggering notification of the observers of
      properties whose value changed between the calls.

      Optional.
    */
    get: {
      defaultValue: undefined,
      type: 'function'
    },

    /**
      A setter function that will be executed when the value of this property is assigned.

      Use this, for example, to modify simulation state when the property is changed.

      Note that this function is not required to store the value in any way; a corresponding getter
      does not need to be defined, although one could be. This setter is normally executed just to
      make sure the correct side effects occur when a property assignment is made.

      The value received by this function is a "raw" value. That is, if the value of this property
      is set "normally", then the value is first passed through the beforeSetTransform, if one is
      defined, and the transformed value is passed to this function. (If that sounds backwards,
      consider "raw" values to be of the type operated on by the simulation engine; transformed
      values are what are visible in the user interface.)

      The set function is called whenever a normal assignment is made to the property, but it may or
      may not be called when the property value is set "behind the scenes" by the setRawValues
      method. It will be called if and onlyh if this property key is present in the hash sent to
      setRawValues *and* the invokeSetterAfterBulkRestore descriptor value for this property is
      true.

      This is useful for distinguishing between properties whose setters must manipulate private
      state variables when they are called, and properties whose setter action operates entirely
      by setting publicly visible
    */
    set: {
      defaultValue: undefined,
      type: 'function'
    },

    /**
      A function that will be called with the new, "raw" value of this property whenever the
      property is assigned to.

      This function *must* return input value if it is correct. If the value is invalid,
      the validate function should throw an exception. Note that custom validate function
      can be used to autmatically "fix" the value (e.g. change lower case to upper case or
      do any other transformation related to notation of the value).

      The validate function is *not* called when the property value is set via setRawValues.
    */
    validate: {
      defaultValue: undefined,
      type: 'function'
    },

    /**
      A callback that is called before assignment to the property. (Exception: it is not called
      when the value is set via the setRawValues method of propertySupport.)

      Use this to detect changes which may cause other property values to need to be updated.

      For convenience, the property key is passed to the callback. The callback's return value is
      discarded.
    */
    beforeSetCallback: {
      defaultValue: undefined,
      type: 'function'
    },

    /**
      A callback that is called after assignment to the property. (Exception: it is not called
      when the value is set via the setRawValues method of propertySupport.)

      Use this to detect changes which may cause other property values to need to be updated.

      For convenience, the property key is passed to the callback. The callback's return value is
      discarded.
    */
    afterSetCallback: {
      defaultValue: undefined,
      type: 'function'
    },

    /**
      A function that is called with the return value of the get method whenever this property value
      is read. The value returned by this function is returned as the value of the property.

      If the transform is not defined, no transform is applied and the value of the property is
      simply the value returned by the get method.

      The expected use of this transform (and the associated beforeSetTransform) is to allow the
      same simulation engine to appear to operate at different length scales. Currently, the MD2D
      engine uses afterGetTransforms to convert values that are nominally in microscopic units (nm,
      for example) to values in a macroscopic unit system (m).
    */
    afterGetTransform: {
      defaultValue: undefined,
      type: 'function'
    },

    /**
      A function that is called to transform the property value to a "raw" value which is passed to
      the set function whenever this property is assigned to.

      If the transform is not defined, no transform is applied and the value that is assigned to the
      property is the value that is passed to the set method.
    */
    beforeSetTransform: {
      defaultValue: undefined,
      type: 'function'
    },

    /**
      If true, the property is considered read-only (and, practically speaking, must have a getter).

      Attempts to assign to the property will throw an error whether the property is directly
      assigned to or a value for the property is passed to the setRawValues method of the
      propertySupport object.

      Note that the native 'writable' property of ES5 Object descriptors does not apply to accessor
      properties (those with setters and getters, such as we construct in this module).
    */
    writable: {
      defaultValue: true,
      type: 'boolean'
    },

    /**
      If true, then the raw value of this property will be included in historyStateRawValues hash.
    */
    includeInHistoryState: {
      defaultValue: false,
      type: 'boolean'
    },

    /**
      If true, and this property's descriptor also includes a set function, then the set function
      will be called when the value of this property is updated via the setRawValues method of the
      PropertySupport object.

      If false, setRawValues will update the property without calling the set method.

      It is useful to set this to false for properties whose setter action operates entirely by
      directly or indirectly manipulating other properties. When setRawValues is used to restore the
      value of those properties during navigation of simulation history, it would be undesirable to
      repeat the setter action as it is entirely accounted for by the value of the other properties.
    */
    invokeSetterAfterBulkRestore: {
      defaultValue: true,
      type: 'boolean'
    },

    /**
      A string that represents the user-defined categorization of this property.

      When the propertySupport object is initialized, it can be passed a list of strings
      containing the different property types the engine wishes to use to categorize its properties.

      PRopertySupport mixes into its target object a method called propertiesOfType which can be
      used to filter the set of properties by category

      (For example, MD2D defines "mainProperties", "viewOptions", "parameters", and "outputs")
    */
    type: {
      defaultValue: undefined,
      type: 'propertyType'
    },

    /**
      An arbitrary object that will be returned when this property's key is passed to the
      getPropertyDescription method of the target object.

      Use this (possibly combined with enumeration and categorization of properties) to expose the
      list of properties to client code for use by e.g,. a UI builder or live scripting help.
    */
    description: {
      defaultValue: undefined
    }
  };

  function validateIsType(type, propertyKey, value) {
    // This is sufficient for functions and strings, which is all we test for.
    if (typeof value !== type) {
      throw new TypeError(propertyKey + " must be a " + type + ".");
    }
  }

  function copy(a) {
    var ret = [];
    a.forEach(function(item) {
      ret.push(item);
    });
    return ret;
  }

  // Constructs a propertySupport object for use by client code. Client code (e.g., models) can maintain a
  // private reference to the propertySupport objectg and delegate property handling to it, and they
  // can optionally call the mixInto method of the propertySupport method to mix in a useful set of
  // public-facing methods and properties.

  // Accepts an args object with an optional 'types' arg, which should be a list of strings that
  // represent user-defined categories of properties.
  return function PropertySupport(args) {

    var propertyTypes = args && args.types && copy(args.types) || [],
        propertyInformation = {},
        cachedPropertiesObjects = {
          all: undefined,
          byType: {}
        },
        cachingIsEnabled = true,
        notificationsAreBatched = false,

        dispatch = d3.dispatch("beforeInvalidatingChange",
                               "afterInvalidatingChange",
                               "afterInvalidatingChangeSequence"),

        invalidatingChangeNestingLevel = 0,
        invalidatingChangeOccurredDuringBatch,
        suppressInvalidationForBatch = false,

        // all properties that were notified while notifications were batched
        changedPropertyKeys = [],

        // all properties with a getter
        computedPropertyKeys = [],

        // all properties for which includeInHistoryState is true
        historyStatePropertyKeys = [],

        // public API
        api;


    // observed properties with a getter
    function observedComputedPropertyKeys() {
      return computedPropertyKeys.filter(function(key) {
        return propertyInformation[key].observers.length > 0;
      });
    }

    function validateIsPropertyType(value) {
      if (propertyTypes.indexOf(value) < 0) {
        throw new TypeError(value + " is not a recognized property type.");
      }
    }

    // Copy the properties described in descriptorProperties from 'descriptor' to return value;
    // validate each value of descriptor according to descriptorProperties[key].type and,
    // if no value is supplied for a given key in 'descriptor' use the default value specified
    // in descriptorProperties[key].default
    function validateDescriptor(descriptor) {
      var ret = {};

      Object.keys(descriptorProperties).forEach(function(key) {
        var descriptorProperty = descriptorProperties[key];

        if (descriptor[key] !== undefined) {
          ret[key] = descriptor[key];
          switch (descriptorProperty.type) {
            case 'boolean':
              ret[key] = !!ret[key];
              break;
            case 'function':
              validateIsType('function', key, descriptor[key]);
              break;
            case 'string':
              validateIsType('string', key, descriptor[key]);
              break;
            case 'propertyType':
              validateIsPropertyType(descriptor[key]);
              break;
          }
        } else {
          ret[key] = descriptorProperty.defaultValue;
        }
      });

      return ret;
    }

    // Given a list of callbacks, invoke each one in order, but skip repeats.
    function notifyCallbacksOnce(callbacks) {
      var called = [];
      callbacks.forEach(function(callback) {
        // TODO: explore ES6 Map/WeakMap shim that would allow this check to happen in O(1)
        if (called.indexOf(callback) < 0) {
          callback();
          called.push(callback);
        }
      });
    }

    // Execute closure after setting a flag which causes the notify function to queue a list of
    // notified properties, rather than notifying their observers immediately. After the closure
    // finishes, notify the observers, making sure to call each callback at most once.
    function withBatchedNotifications(closure) {
      var callbacks = [];

      notificationsAreBatched = true;
      closure();
      notificationsAreBatched = false;

      changedPropertyKeys.forEach(function(key) {
        propertyInformation[key].observers.forEach(function(callback) {
          callbacks.push(callback);
        });
      });
      changedPropertyKeys = [];
      notifyCallbacksOnce(callbacks);
    }

    // Notify observers of the passed-in property immediately if notifications are not batched
    // (see withBatchedNotifications), or else queue the passed-in property key for later
    // notification
    function notify(key) {
      if (notificationsAreBatched) {
        changedPropertyKeys.push(key);
      } else {
        notifyCallbacksOnce(propertyInformation[key].observers);
      }
    }

    // Note this does not respect batched notifications, as property descriptions are not expected
    // to be updated en masse during an engine tick as are property values.
    function notifyPropertyDescriptionObservers(key) {
      notifyCallbacksOnce(propertyInformation[key].propertyDescriptionObservers);
    }

    // Private implementation of the getter for the property specified by 'key'. Handles caching
    // concerns, but not afterGetTransform, etc.
    function get(key) {
      var info = propertyInformation[key];

      if (!info.descriptor.get) {
        return info.cachedValue;
      }

      if (cachingIsEnabled) {
        if (!info.hasCachedValue) {
          info.hasCachedValue = true;
          info.cachedValue = info.descriptor.get();
        }
        return info.cachedValue;
      }
      return info.descriptor.get();
    }

    // Private implementation of the setter for the property specified by 'key'. Handles caching
    // and the writable check (which, remember, is always applied) but does not handle observer
    // notification, validation, the beforeSetTransform, or beforeSet/afterSet callbacks.
    function set(key, value) {
      var info = propertyInformation[key];

      if (!info.descriptor.writable) {
        throw new Error("Attempt to set read-only property " + key);
      }

      if (info.descriptor.get && !cachingIsEnabled) {
        info.hasCachedValue = false;
      } else {
        info.hasCachedValue = true;
        info.cachedValue = value;
      }
    }

    function setPropertyDescription(key, description) {
      var info = propertyInformation[key];

      info.descriptor.description = description;
      notifyPropertyDescriptionObservers(key);
    }

    function invalidateCachedPropertiesObjects(type) {
      cachedPropertiesObjects.all = undefined;
      if (type) {
        cachedPropertiesObjects.byType[type] = undefined;
      }
    }

    // This is the meat. Adds an enumerable property to the properties object returned by the
    // propertySupport object, with custom getters and setters that implement the behavior supported
    // by this module.
    function constructProperty(object, key) {
      var info = propertyInformation[key];

      Object.defineProperty(object, key, {
        enumerable:   true,
        configurable: false,

        // This is the publicly-accessible getter for the property. This is invoked whenever the
        // property is read via code such as `var value = model.properties[key]`, or when the `get`
        // method mixed into the target is called (this might look like `model.get(key)`). It is
        // not invoked when a 'raw values' hash is constructed by the historyStateRawValues or
        // rawValues property accessors.
        get: function() {
          var value = get(key);
          if (info.descriptor.afterGetTransform) {
            value = info.descriptor.afterGetTransform(value);
          }
          return value;
        },

        // This is the publicly-accessible setter for the property. It is invoked whenever the
        // property is assigned to via code such as `model.properties[key] = value;`, or when the
        // `set` method mixed into the target is called (this might look like `model.set(key,
        // value)`). It is never invoked when the setRawValues method of the propertySupport object
        // is called.
        //
        // If beforeSetCallback or afterSetCallback properties have been defined on propertySupport,
        // then they will be called, respectively, before and after the body of this function
        // executes. Note again that setRawValues bypasses these callbacks.
        set: function(value) {
          if (info.descriptor.beforeSetCallback) {
            info.descriptor.beforeSetCallback();
          }

          if (info.descriptor.beforeSetTransform) {
            value = info.descriptor.beforeSetTransform(value);
          }
          if (info.descriptor.validate) {
            value = info.descriptor.validate(value);
          }

          set(key, value);

          if (info.descriptor.set) {
            info.descriptor.set(value);
          }

          if (info.descriptor.afterSetCallback) {
            info.descriptor.afterSetCallback();
          }

          notify(key);
        }
      });
    }

    // Private support for the `properties` and `propertiesOfType` accessor and method of the
    // propertySupport object. Returns the cached properties object if one exists, or constructs a
    // new one. Note that adding a property to the list of properties invalidates the cached object,
    // forcing construction of a new one when it is requested.
    function getPropertiesObject(type) {
      var object = type ? cachedPropertiesObjects.byType[type] : cachedPropertiesObjects.all;

      if (!object) {
        object = {};
        Object.keys(propertyInformation).forEach(function(key) {
          if (!type || type === propertyInformation[key].descriptor.type) {
            constructProperty(object, key);
          }
        });

        if (Object.seal) {
          Object.seal(object);
        }

        if (type) {
          cachedPropertiesObjects.byType[type] = object;
        } else {
          cachedPropertiesObjects.all = object;
        }
      }
      return object;
    }

    // The public methods and properties of the propertySupport object
    api = {

      /**
        Mixes a useful set of methods and properties into the target object. Lab models are expected
        to provide themselves as the target, i.e., mix these methods/properties into themselves.
      */
      mixInto: function(target) {

        /**
          The 'properties' property mixed into 'target' is a sealed Object whose enumerable
          properties are all the properties defined by calls to the defineProperty method of the
          propertySupport object. Creating this object is the main feature of the PropertySupport
          module.

          Reading the value of a computed property of the 'properties' object causes that value to
          be cached, unless the `enableCaching` property of the propertySupport object is false. The
          cached value is returned on subsequent reads, unless `enableCaching` is set to false,
          or `deleteComputedPropertyCachedValues` is called.

          Assigning to a property of the 'properties' object always triggers the observers of that
          property, if any.

          Because the 'properties' object is sealed, if `defineProperty` is subsequently called, the
          value of the 'properties' property will be updated to a new object containing the updated
          set of properties.
        */
        Object.defineProperty(target, 'properties', {
          configurable: false,
          enumerable: true,
          get: getPropertiesObject
        });

        /**
          The 'propertiesOfType' method mixed in to 'target' returns a sealed Object whose
          enumerable properties are all the properties defined by calls to the defineProperty
          method with the value 'type' as the type descriptor option.

          These properties behave the same as properties of the 'properties' object.
        */
        target.propertiesOfType = function(type) {
          return getPropertiesObject(type);
        };

        /**
          The 'set' method mixed into 'target' sets the value of one or more properties.

          Calling `target.set(key, value)` is equivalent to `target.properties[key] = value`

          However, if the first argument is a hash of properties, then the hash is treated as a
          set of key-value pairs to be assigned. In that case, observer notification is delayed
          until after all property values in the hash have been assigned.
        */
        target.set = function(key, value) {
          var hash;
          if (typeof key === 'string') {
            target.properties[key] = value;
          } else {
            hash = key;
            withBatchedNotifications(function() {
              Object.keys(hash).forEach(function(key) {
                target.properties[key] = hash[key];
              });
            });
          }
        };

        /**
          The 'get' method mixed into target reads the value of one property.

          Calling `target.get(key)` is equivalent to accessing `target.properties[key]`
        */
        target.get = function(key) {
          return target.properties[key];
        };

        // This is the publicly-accessible setter for 'freezing' the property.
        target.freeze = function(key) {
          var description = target.getPropertyDescription(key);
          description.setFrozen(true);
          setPropertyDescription(key, description);
        };

        // This is the publicly-accessible setter for 'un-freezing' the property.
        target.unfreeze = function(key) {
          var description = target.getPropertyDescription(key);
          description.setFrozen(false);
          setPropertyDescription(key, description);
        };

        /**
          The 'addObserver' method mixed into 'target' adds 'callback' to the end of the list of
          property observers of the property specified by 'key'. Note that adding a callback more
          than once to the observer list for a given property has no effect.

          Whenever the property 'key' is assigned to, the callback will be called. As noted above,
          sometimes property assignment is batched (e.g., by passing a hash to`target.set`). When
          this is the case, 'callback' is guaranteed to be called only once after the batched
          assignment, regardless of how many keys it is registered for. (Of course, if one of those
          observers then assigns to a property observed by 'callback', a second call will occur.)

          If 'key' represents a computed property, then observer notification is supported but
          happends according to a different cycle. Specifically, notification of the observer will
          happen if the value of the property changes between paired calls to
          `storeComputedProperties` and `notifyChangedComputedProperties`, or whenever
          `notifyAllComputedProperties` is called (regardless of the current or previous value of
          the property). As with batched property assignment, each callback is guaranteed to be
          called directly by `notifyAllComputedProperties` or `notifyChangedComputedProperties`
          at most once per invocation.

          Notification is never triggered by simply accessing the property, regardless of whether or
          not the access causes the property to be recalcuated

          Note that there are only 2 arguments accepted by addObserver; it does not support
          'this'-binding to a target object.
        */
        target.addObserver = function(key, callback) {
          if (!propertyInformation[key]) {
            return;
          }
          var observers = propertyInformation[key].observers;
          if (observers.indexOf(callback) < 0) {
            observers.push(callback);
          }
        };

        /**
          The 'removeObserver' method mixed into 'target' removes 'callback' from the list of
          callbacks registered for the propery specified by key.
        */
        target.removeObserver = function(key, callback) {
          var observers = propertyInformation[key].observers,
              index = observers.indexOf(callback);

          if (index >= 0) {
            observers.splice(index, 1);
          }
        };

        /**
          The 'addPropertyDescriptionObserver' method mixed into 'target' adds 'callback' to the end
          of the list of property-description observers of the property specified by key. Note that
          adding a callback more than once to the property-description observer list for a given
          property has no effect.

          Property-description observers are called immediately when the observed property's
          description object is reassigned. Note that observing of mutation of the description
          object is not supported; to change a property's description after the property is created,
          always pass a property description object to propertySupport.setPropertyDescription.
        */
        target.addPropertyDescriptionObserver = function(key, callback) {
          if (!propertyInformation[key]) {
            return;
          }
          var observers = propertyInformation[key].propertyDescriptionObservers;
          if (observers.indexOf(callback) < 0) {
            observers.push(callback);
          }
        };

        target.removePropertyDescriptionObserver = function(key, callback) {
          var observers = propertyInformation[key].propertyDescriptionObservers,
              index = observers.indexOf(callback);

          if (index >= 0) {
            observers.splice(index, 1);
          }
        };

        /**
          The 'isPropertyWritable' method mixed into 'target' returns information whether
          the property named 'key' is writable.
         */
        target.hasProperty = function(key) {
          return propertyInformation[key] != null;
        };

        /**
          The 'isPropertyWritable' method mixed into 'target' returns information whether
          the property named 'key' is writable.
         */
        target.isPropertyWritable = function(key) {
          return !!propertyInformation[key] && propertyInformation[key].descriptor.writable;
        };

        /**
          The 'getPropertyDescription' method mixed into 'target' simply returns the object passed
          in as the 'description' property of the descriptor passed to `defineProperty` when the
          property named 'key' was defined.
        */
        target.getPropertyDescription = function(key) {
          if (propertyInformation[key] && propertyInformation[key].descriptor) {
            return propertyInformation[key].descriptor.description;
          }
          return null;
        };

        /**
          The 'getPropertyType' method mixed into 'target' simply returns the 'type' value passed
          in as the 'type' property of the descriptor passed to 'defineProperty'when the property
          named 'key' was defined.
        */
        target.getPropertyType = function(key) {
          if (propertyInformation[key] && propertyInformation[key].descriptor) {
            return propertyInformation[key].descriptor.type;
          }
          return null;
        };

        /**
          The 'getPropertyValidateFunc' method mixed into 'target' simply returns the 'validate' function
          passed in as the 'validate' property of the descriptor passed to 'defineProperty' when the
          property named 'key' was defined.
        */
        target.getPropertyValidateFunc = function(key) {
          if (propertyInformation[key] && propertyInformation[key].descriptor) {
            return propertyInformation[key].descriptor.validate;
          }
          return null;
        };

        /**
          The 'makeInvalidatingChange' method mixed into 'target' lets client code perform an action
          that will invalidate all computed properties.
         */
        target.makeInvalidatingChange = function(closure) {
          api.invalidatingChangePreHook();
          if (closure) {
            closure();
          }
          api.invalidatingChangePostHook();
        };

        // TODO: probably it's unnecessary, addObserver can support multiple
        // properties instead.
        target.addPropertiesListener = function(properties, callback) {
          if (typeof properties === 'string') {
            target.addObserver(properties, callback);
          } else {
            properties.forEach(function(property) {
              target.addObserver(property, callback);
            });
          }
        };
      },

      /**
        The defineProperty method allows the client object to define a new property named 'key'. The
        'descriptor' property should be a hash containing property descriptors; see the comments on
        the descriptorProperties constant, above.
      */

      defineProperty: function(key, descriptor) {
        descriptor = validateDescriptor(descriptor || {});

        propertyInformation[key] = {
          descriptor: descriptor,
          observers: [],
          propertyDescriptionObservers: [],
          hasCachedValue: false,
          cachedValue: undefined,
          previousValue: undefined
        };

        if (descriptor.get) {
          computedPropertyKeys.push(key);
        }
        if (descriptor.includeInHistoryState) {
          historyStatePropertyKeys.push(key);
        }

        invalidateCachedPropertiesObjects(descriptor.type);
      },

      /**
        Set the PropertyDescription associated with 'key' to 'description' and notify any
        property-description observers (added via target.addPropertyDescriptionObserver())
      */
      setPropertyDescription: function(key, description) {
        setPropertyDescription(key, description);
      },

      /**
        The 'deleteComputedPropertyCachedValues' method removes the cached value of all computed
        properties (i.e., all properties with getters.)

        The next access of the property (either caused directly by code that explicitly accesses the
        property, or indirectly by `notifyChangedComputedProperties`, which retrieves the current
        value of all observed computed properties) will cause a recomputation of the property.
      */
      deleteComputedPropertyCachedValues: function() {
        computedPropertyKeys.forEach(function(key) {
          propertyInformation[key].hasCachedValue = false;
          propertyInformation[key].cachedValue = undefined;
        });
      },

      /**
        The 'storeComputedProperties' method retrieves the current value of all computed properties,
        respecting any previously-cached value, and stores it in a secondary cache for subsequent
        comparison to an updated value, by `notifyChangedComputedProperties`.

        Normally, one would call this method prior to updating the simulation clock, and then call
        `deleteComputedPropertyCachedValues` and notifyChangedComputedProperties` after updating
        the simulation clock.
      */
      storeComputedProperties: function() {
        observedComputedPropertyKeys().forEach(function(key) {
          propertyInformation[key].previousValue = get(key);
        });
      },

      /**
        Retrieves the current value of all computed properties, respecting any cached value it
        finds, and compares them to the previous values of the properties stored by
        `storeComputedProperties`

        Notifies the observers of any properties whose values differ from the previous value. Note
        that observers are called strictly after all computed property values are calculated, and
        each observer callback is guaranteed to be called directly by this method only once per
        invocation.

        (However, it would be possible for any given callback to be called again as a side effect of
        previous observers.)

        Note that, because this method observes the cache, you probably want to call
        `deleteComputedPropertyCachedValues` after calling `storeComputedProperties`,
      */
      notifyChangedComputedProperties: function() {
        withBatchedNotifications(function() {
          observedComputedPropertyKeys().forEach(function(key) {
            if (get(key) !== propertyInformation[key].previousValue) {
              notify(key);
            }
            propertyInformation[key].previousValue = undefined;
          });
        });
      },

      /**
        Blanket-notifies the observers of all computed properties. As described above, each observer
        callback will only be called directly by this method only once per invocation, but the side
        effects of some observer callbacks may result in subsequent calls to any given observer
        callback.
      */
      notifyAllComputedProperties: function() {
        withBatchedNotifications(function() {
          observedComputedPropertyKeys().forEach(function(key) {
            notify(key);
          });
        });
      },

      /**
        The 'properties' object is the main object containing the properties defined using this
        module. This is the same object that is mixed into the mixin target, and it is described
        above in detail.
      */
      get properties() {
        return getPropertiesObject();
      },

      /**
        The 'propertiesOfType' method behaves the same as the `propertiesOfType` method mixed into
        the mixin target, and it is describd above.
      */
      propertiesOfType: function(type) {
        return getPropertiesObject(type);
      },

      /**
        The enableCaching property indicates whether computed property values should be cached.
        When multiple cycles of property changes are triggered by a single change to the simulation
        state, you may want to turn off property caching until all cycles complete.
      */

      get enableCaching() {
        return cachingIsEnabled;
      },

      set enableCaching(value) {
        cachingIsEnabled = !!value;
      },

      /**
        The 'historyStateRawValues' property is a hash of key-value pairs of those properties which
        have the `includeInHistoryState` descriptor property set to true.

        The underlying values are 'raw' values, i.e., those which have been passed through the
        beforeSetTransform.
      */
      get historyStateRawValues() {
        var ret = {};
        historyStatePropertyKeys.forEach(function(key) {
          ret[key] = get(key);
        });
        return ret;
      },

      /**
        The 'rawValues' property is a hash of key-value pairs of all properties.

        The underlying values are 'raw' values, i.e., those which have been passed through the
        beforeSetTransform.
      */
      get rawValues() {
        var ret = {};
        Object.keys(propertyInformation).forEach(function(key) {
          ret[key] = get(key);
        });
        return ret;
      },

      /**
        The 'setRawValues' method accepts a hash of key-value pairs of some properties.

        Unlike the argument accepted by the 'set' method mixed into the mixin target, the values are
        expected to be 'raw' values, i.e., those which have already passed through the
        beforeSetTransform.

        Furthermore, notification of observers is only triggered for those properties whose value
        changed. This is because setRawValues is expected to be used as a system interface for
        restoring past states of the simulation, e.g., rewinding a simulation, and it would be
        undesirable to notify every observer, every time a history state was revisited.

        Additionally, for each property in the passed-in hash, the 'internal' setter is called if
        and only if that property has its `invokeSetterAfterBulkRestore` descriptor property set
        to true.
      */
      setRawValues: function(values) {
        withBatchedNotifications(function() {
          Object.keys(values).forEach(function(key) {
            var info = propertyInformation[key];
            if (!info) {
              return;
            }
            // During bulk state restoration, only actually changed values should trigger observers!
            if (get(key) !== values[key]) {
              notify(key);
            }
            set(key, values[key]);
            if (info.descriptor.invokeSetterAfterBulkRestore && info.descriptor.set) {
              info.descriptor.set(get(key));
            }
          });
        });
      },

      invalidatingChangePreHook: function() {

        // Only the first invalidating change during a batch runs the "pre hook".
        if (suppressInvalidationForBatch && invalidatingChangeOccurredDuringBatch) {
          return;
        }

        invalidatingChangeOccurredDuringBatch = true;

        if (invalidatingChangeNestingLevel === 0) {
          api.storeComputedProperties();
          api.deleteComputedPropertyCachedValues();
          api.enableCaching = false;
        }
        invalidatingChangeNestingLevel++;

        dispatch.beforeInvalidatingChange();
      },

      invalidatingChangePostHook: function() {
        if (suppressInvalidationForBatch) return;

        invalidatingChangeNestingLevel--;

        dispatch.afterInvalidatingChange();

        if (invalidatingChangeNestingLevel === 0) {
          api.enableCaching = true;
          api.notifyChangedComputedProperties();

          dispatch.afterInvalidatingChangeSequence();
        }
      },

      // N.B. We don't currently handle nested batches. This may be a problem.
      startBatch: function() {
        invalidatingChangeOccurredDuringBatch = false;
        suppressInvalidationForBatch = true;
      },

      endBatch: function() {
        suppressInvalidationForBatch = false;
        if (invalidatingChangeOccurredDuringBatch) {
          api.invalidatingChangePostHook();
        }
      },

      on: function (type, listener) {
        dispatch.on(type, listener);
      }
    };

    return api;
  };
});

/*global define, d3 */
/*jshint eqnull:true, boss:true */

define('common/property-description',['require','underscore'],function(require) {

  var _ = require('underscore');

  function isUndefined(val) {
    return val === "";
  }

  function PropertyDescription(unitDefinition, descriptionHash) {
    var u;

    this._descriptionHash = descriptionHash;
    this._label = descriptionHash.label || "";

    this._unitName         = "";
    this._unitPluralName   = "";
    this._unitAbbreviation = "";

    if (descriptionHash.unitType) {
      if ( !(u = unitDefinition.units[descriptionHash.unitType]) ) {
        throw new Error("PropertyDescription: couldn't find unitType " + descriptionHash.unitType + " in the supplied units definition.");
      }
      this._unitType         = descriptionHash.unitType;
      this._unitName         = u.name;
      this._unitPluralName   = u.pluralName;
      this._unitAbbreviation = u.symbol;
    }

    // allow overriding the unit properties, or specifying custom ones for which there is no
    // current unit definition.
    if (descriptionHash.unitName) this._unitName = descriptionHash.unitName;
    if (descriptionHash.unitPluralName) this._unitPluralName = descriptionHash.unitPluralName;
    if (descriptionHash.unitAbbreviation) this._unitAbbreviation = descriptionHash.unitAbbreviation;

    this._min = descriptionHash.min;
    this._max = descriptionHash.max;

    this._frozen = descriptionHash.frozen;

    this.setFormat(descriptionHash.format);
  }

  PropertyDescription.prototype.getHash = function() {
    return _.extend(
      _.reject({
          unitName:         this.getUnitName(),
          unitPluralName:   this.getUnitPluralName(),
          unitAbbreviation: this.getUnitAbbreviation()
        }, isUndefined),
      this._descriptionHash);
  };

  PropertyDescription.prototype.getLabel = function() {
    return this._label;
  };

  PropertyDescription.prototype.getUnitType = function() {
    return this._unitType;
  };

  PropertyDescription.prototype.getUnitName = function() {
    return this._unitName;
  };

  PropertyDescription.prototype.getUnitPluralName = function() {
    return this._unitPluralName;
  };

  PropertyDescription.prototype.getUnitAbbreviation = function() {
    return this._unitAbbreviation;
  };

  PropertyDescription.prototype.getMin = function() {
    return this._min;
  };

  PropertyDescription.prototype.getMax = function() {
    return this._max;
  };

  PropertyDescription.prototype.getFrozen = function() {
    return this._frozen;
  };

  PropertyDescription.prototype.setFrozen = function(b) {
    this._frozen = b;
  };

  PropertyDescription.prototype.setFormat = function(s) {
    if (s) this._formatter = d3.format(s);
    else   this._formatter = function (val) { return val; };
  };

  PropertyDescription.prototype.format = function(val, opts) {
    opts = opts || {};

    var formatter,
        formattedVal,
        plural,
        abbreviated = true;

    if (opts.format) {
      if (opts.format === this._lastFormat) {
        formatter = this._lastFormatter;
      } else {
        formatter = d3.format(opts.format);
        this._lastFormat = opts.format;
        this._lastFormatter = formatter;
      }
    } else {
      formatter = this._formatter;
    }

    formattedVal = formatter(val);

    if (opts && opts.abbreviated != null) abbreviated = opts.abbreviated;

    if (abbreviated) {
      return formattedVal + " " + this._unitAbbreviation;
    }

    plural = parseFloat(formattedVal) !== 1;
    return formattedVal + " " + (plural ? this._unitPluralName : this._unitName);
  };

  return PropertyDescription;
});

/*global define: false */

define('common/parameter-support',['require','common/property-description'],function (require) {

  var PropertyDescription  = require('common/property-description');

  return function ParameterSupport(args) {
    var propertySupport = args.propertySupport,
        unitsDefinition = args.unitsDefinition || {};

    return {
      mixInto: function(target) {

        /**
          Define a property of the model to be treated as a custom parameter. Custom parameters are
          (generally, user-defined) read/write properties that trigger a setter action when set, and
          whose values are automatically persisted in the tick history.

          Because custom parameters are not intended to be interpreted by the engine, but instead simply
          *represent* states of the model that are otherwise fully specified by the engine state and
          other properties of the model, and because the setter function might not limit itself to a
          purely functional mapping from parameter value to model properties, but might perform any
          arbitrary stateful change, (stopping the model, etc.), the setter is NOT called when custom
          parameters are updated by the tick history.
        */
        target.defineParameter = function(key, descriptionHash, setter) {
          var descriptor = {
                type: 'parameter',
                includeInHistoryState: true,
                invokeSetterAfterBulkRestore: false,
                description: new PropertyDescription(unitsDefinition, descriptionHash),
                beforeSetCallback: propertySupport.invalidatingChangePreHook,
                afterSetCallback: propertySupport.invalidatingChangePostHook
              };

          // In practice, some parameters are meant only to be observed, and have no setter
          if (setter) {
            descriptor.set = function(value) {
              setter.call(target, value);
            };
          }
          propertySupport.defineProperty(key, descriptor);
        };
      }
    };
  };
});

define('cs',{load: function(id){throw new Error("Dynamic load not allowed: " + id);}});

(function() {

  define('cs!common/filters/running-average-filter',['require'],function(require) {
    /*
      Filter implementing running average.
      This filter assumes that provided samples are samples of some unknown function.
      The function is interpolated using linear interpolation. Later, integration is
      used to get mean value of the function on the given time period.
    */

    var RunningAverageFilter;
    return RunningAverageFilter = (function() {
      /*
          Construct new Running Average Filter.
          @periodLength - length of time period, in fs, which is used to calculate averaged value.
      */

      function RunningAverageFilter(periodLength) {
        this.periodLength = periodLength;
        this._value = [];
        this._time = [];
        this._idx = -1;
        this._maxBufferLength = this.periodLength;
      }

      /*
          Add a new sample of a function which is going to be averaged.
          Note that samples must be provided in order, sorted by time.
          @t - time
          @val - value of the sample
      */


      RunningAverageFilter.prototype.addSample = function(t, val) {
        var _results;
        if (this._time[this._idx] === t) {
          this._value[this._idx] = val;
          return;
        } else if (this._time[this._idx] > t) {
          throw new Error("RunningAverageFilter: cannot add sample with @_time less than previous sample.");
        }
        this._idx++;
        this._value.push(val);
        this._time.push(t);
        _results = [];
        while (this._value.length > this._maxBufferLength) {
          this._time.shift();
          this._value.shift();
          _results.push(this._idx--);
        }
        return _results;
      };

      /*
          Return averaged value n the specified time period (using available samples).
      */


      RunningAverageFilter.prototype.calculate = function() {
        var i, minTime, minVal, timeDiff, timeSum, valSum;
        minTime = Math.max(this._time[this._idx] - this.periodLength, 0);
        valSum = 0;
        timeSum = 0;
        i = this._idx;
        while (i > 0 && this._time[i - 1] >= minTime) {
          timeDiff = this._time[i] - this._time[i - 1];
          timeSum += timeDiff;
          valSum += timeDiff * (this._value[i - 1] + this._value[i]) / 2.0;
          i--;
        }
        if (i > 0 && this._time[i] > minTime && this._time[i - 1] < minTime) {
          timeDiff = this._time[i] - minTime;
          timeSum += timeDiff;
          minVal = this._value[i - 1] + (this._value[i] - this._value[i - 1]) * (minTime - this._time[i - 1]) / (this._time[i] - this._time[i - 1]);
          valSum += timeDiff * (this._value[i] + minVal) / 2.0;
        }
        if (timeSum) {
          return valSum / timeSum;
        } else {
          return this._value[0] || 0;
        }
      };

      /*
          Return current length of the buffers used to store samples.
      */


      RunningAverageFilter.prototype.getCurrentBufferLength = function() {
        return this._value.length;
      };

      /*
          Set limit of the buffer which stores samples.
      */


      RunningAverageFilter.prototype.setMaxBufferLength = function(maxLength) {
        return this._maxBufferLength = maxLength;
      };

      /*
          Return current time.
      */


      RunningAverageFilter.prototype.getCurrentTime = function() {
        return this._time[this._idx];
      };

      /*
          Return current step index.
      */


      RunningAverageFilter.prototype.getCurrentStep = function() {
        return this._idx;
      };

      /*
          Set current step to @location.
          It allows to get average value of the function in various moments in time.
      */


      RunningAverageFilter.prototype.setCurrentStep = function(location) {
        if (location < -1 || location >= this._value.length) {
          throw new Error("RunningAverageFilter: cannot seek, location outside available range.");
        }
        return this._idx = location;
      };

      /*
          Remove all samples *after* @location.
      */


      RunningAverageFilter.prototype.invalidate = function(location) {
        this._value.length = location + 1;
        this._time.length = location + 1;
        return this._idx = location;
      };

      /*
          Reset filter
      */


      RunningAverageFilter.prototype.reset = function() {
        this._values.length = 0;
        this._time = 0;
        return this._idx = 0;
      };

      return RunningAverageFilter;

    })();
  });

}).call(this);


(function() {

  define('cs!common/filters/simple-period-filter',['require'],function(require) {
    /*
      Filter implementing that calculates period for a simple period data.
    */

    var SimplePeriodFilter;
    return SimplePeriodFilter = (function() {
      /*
          Construct new Running Average Filter.
          @periodLength - length of time period, in fs, which is used to calculate averaged value.
      */

      function SimplePeriodFilter() {
        this._values = [];
        this._time = [];
        this._idx = -1;
        this._zero_crossing_buffer = [];
        this._period_buffer = [];
        this._maxBufferLength = 8192;
      }

      /*
          Add a new sample of a function which is going to be averaged.
          Note that samples must be provided in order, sorted by time.
          @t - time
          @val - value of the sample
      */


      SimplePeriodFilter.prototype.addSample = function(t, val) {
        var previous_value, previous_zero_crossing;
        if (this._time[this._idx] === t) {
          this._values[this._idx] = val;
          return;
        } else if (this._time[this._idx] > t) {
          console.log("Error: RunningAverageFilter: cannot add sample with @_time less than previous sample.");
          return;
        }
        this._idx++;
        this._values.push(val);
        this._time.push(t);
        if (this._values.length > 1) {
          previous_value = this._values[this._values.length - 2];
        }
        while (this._values.length > this._maxBufferLength) {
          this._time.shift();
          this._values.shift();
          this._idx--;
        }
        if ((previous_value < 0 && val > 0) || (previous_value > 0 && val < 0)) {
          this._zero_crossing_buffer.push(t);
          if (this._zero_crossing_buffer.length > 1) {
            previous_zero_crossing = this._zero_crossing_buffer[this._zero_crossing_buffer.length - 2];
            this._period_buffer.push(t - previous_zero_crossing);
          }
          while (this._zero_crossing_buffer.length > 128) {
            this._zero_crossing_buffer.shift();
            this._period_buffer.shift();
          }
        }
      };

      /*
          Return averaged period.
      */


      SimplePeriodFilter.prototype.calculate = function() {
        var average, sum;
        if (this._period_buffer.length < 1) {
          return 0;
        }
        if (this._period_buffer.length === 1) {
          return this._period_buffer[0] * 2;
        }
        sum = this._period_buffer.reduce(function(x, y) {
          return x + y;
        });
        average = sum / this._period_buffer.length;
        return average * 2;
      };

      /*
          Return current length of the buffers used to store samples.
      */


      SimplePeriodFilter.prototype.getCurrentBufferLength = function() {
        return this._values.length;
      };

      /*
          Set limit of the buffer which stores samples.
      */


      SimplePeriodFilter.prototype.setMaxBufferLength = function(maxLength) {
        return this._maxBufferLength = maxLength;
      };

      /*
          Return current time.
      */


      SimplePeriodFilter.prototype.getCurrentTime = function() {
        return this._time[this._idx];
      };

      /*
          Return current step index.
      */


      SimplePeriodFilter.prototype.getCurrentStep = function() {
        return this._idx;
      };

      /*
          Set current step to @location.
          It allows to get average value of the function in various moments in time.
      */


      SimplePeriodFilter.prototype.setCurrentStep = function(location) {
        if (location < -1 || location >= this._values.length) {
          throw new Error("SimplePeriodFilter: cannot seek, location outside available range.");
        }
        return this._idx = location;
      };

      /*
          Remove all samples *after* @location.
      */


      SimplePeriodFilter.prototype.invalidate = function(location) {
        if (location) {
          this._values.length = location + 1;
          this._time.length = location + 1;
          return this._idx = location;
        } else {
          return this.reset();
        }
      };

      /*
          Reset filter
      */


      SimplePeriodFilter.prototype.reset = function() {
        this._values.length = 0;
        this._time.length = 0;
        this._idx = 0;
        this._zero_crossing_buffer.length = 0;
        this._period_buffer.length = 0;
      };

      return SimplePeriodFilter;

    })();
  });

}).call(this);

/*global define: false */

define('common/output-support',['require','common/property-description','cs!common/filters/running-average-filter','cs!common/filters/simple-period-filter'],function (require) {

  var PropertyDescription  = require('common/property-description'),
      RunningAverageFilter = require('cs!common/filters/running-average-filter'),
      SimplePeriodFilter   = require('cs!common/filters/simple-period-filter');

  return function OutputSupport(args) {
    var propertySupport = args.propertySupport,
        unitsDefinition = args.unitsDefinition || {},
        tickHistory     = args.tickHistory || null,

        filteredOutputs = [];

    function updateFilteredOutputs() {
      filteredOutputs.forEach(function(output) {
        output.addSample();
      });
    }

    function resetFilteredOutputs() {
      filteredOutputs.forEach(function(output) {
        output.reset();
      });
    }

    // TODO: is it necessary? It follows the old solution.
    // In theory filtered outputs could be updated only on time change
    // or on filtered property value change. Check it!
    propertySupport.on("afterInvalidatingChange.output-support", updateFilteredOutputs);

    return {
      mixInto: function(target) {

        /**
          Add an "output" property to the model. Output properties are expected to change at every
          model tick, and may also be changed indirectly, outside of a model tick, by a change to model
          properties or the atom, element, etc. properties.

          `key` should be the name of the output. The property value will be accessed by
          `model.get(<key>);`

          `description` should be a hash of metadata about the property.

          `getter` should be a no-arg function which calculates the property value. These values are not
          translated after getter returns because we expect that most output getters are authored
          scripts, which operate entirely with already-translated units. Therefore, getters defined
          internally in modeler.js needs to make sure to translate any "model units" values out of the
          model-unit domain.
        */
        target.defineOutput = function(key, descriptionHash, getter) {
          propertySupport.defineProperty(key, {
            type: 'output',
            writable: false,
            get: getter,
            includeInHistoryState: false,
            description: new PropertyDescription(unitsDefinition, descriptionHash)
          });
        };


        /**
          Add an "filtered output" property to the model. This is special kind of output property, which
          is filtered by one of the built-in filters based on time (like running average). Note that filtered
          outputs do not specify calculate function - instead, they specify property which should filtered.
          It can be another output, model parameter or custom parameter.

          Filtered output properties are extension of typical output properties. They share all features of
          output properties, so they are expected to change at every model tick, and may also be changed indirectly,
          outside of a model tick, by a change to the model parameters or to the configuration of atoms and other
          objects in the model.

          `key` should be the name of the parameter. The property value will be accessed by
          `target.get(<key>);`

          `description` should be a hash of metadata about the property. Right now, these metadata are not
          used. However, example metadata include the label and units name to be used when graphing
          this property.

          `filteredPropertyKey` should be name of the basic property which should be filtered.

          `type` should be type of filter, defined as string. For now only "RunningAverage" and "SimplePeriod"
          are supported.

          `period` should be number defining length of time period used for calculating filtered value. It should
          be specified in femtoseconds.

        */
        target.defineFilteredOutput = function(key, description, filteredPropertyKey, type, period) {
          var filter, initialValue;

          if (type === "RunningAverage") {
            filter = new RunningAverageFilter(period);
          } else if (type === "SimplePeriod") {
            filter = new SimplePeriodFilter();
          } else {
            throw new Error("FilteredOutput: unknown filter type " + type + ".");
          }

          // Add initial sample
          initialValue = target.properties[filteredPropertyKey];
          if (initialValue === undefined || isNaN(Number(initialValue))) {
            throw new Error("FilteredOutput: property is not a valid numeric value or it is undefined.");
          }
          filter.addSample(target.properties.time, initialValue);

          if (tickHistory) {
            // Create simple adapter implementing TickHistoryCompatible Interface
            // and register it in tick history.
            tickHistory.registerExternalObject({
              push: function () {
                // Filtered outputs are updated only at the end of tick() operation,
                // during tickHistory.push() call. So they are *not* updated
                // immediately after property change, e.g. using model.set("prop", 5).
                // Filtered ouput bound to "prop" property will reflect this change
                // in the next tick.
                filter.addSample(target.properties.time, target.properties[filteredPropertyKey]);
              },
              extract: function (idx) {
                filter.setCurrentStep(idx);
              },
              invalidate: function (idx) {
                filter.invalidate(idx);
              },
              setHistoryLength: function (length) {
                filter.setMaxBufferLength(length);
              },
              reset: function() {
                filter.reset();
              }
            });
          } else {
            filteredOutputs.push({
              addSample: function() {
                filter.addSample(target.properties.time, target.properties[filteredPropertyKey]);
              }
            });
          }

          // Extend description to contain information about filter
          description.property = filteredPropertyKey;
          description.type = type;
          description.period = period;

          target.defineOutput(key, description, function () {
            return filter.calculate();
          });
        };

        /**
          Call this method after moving to a different model time (e.g., after stepping the model
          forward or back, seeking to a different time, or on model initialization) to update all output
          properties and notify their listeners. This method is more efficient for that case than
          updateOutputPropertiesAfterChange because it can assume that all output properties are
          invalidated by the model step. It therefore does not need to calculate any output property
          values; it allows them to be evaluated lazily instead. Property values are calculated when and
          if listeners request them. This method also guarantees that all properties have their updated
          value when they are requested by any listener.

          Technically, this method first updates the 'viewAtoms' array and macrostate variables, then
          invalidates any  cached output-property values, and finally notifies all output-property
          listeners.

          Note that this method and updateOutputPropertiesAfterChange are the only methods which can
          flush the cached value of an output property. Therefore, be sure to not to make changes
          which would invalidate a cached value without also calling one of these two methods.
        */
        target.updateAllOutputProperties = function () {
          propertySupport.deleteComputedPropertyCachedValues();
          propertySupport.notifyAllComputedProperties();
          updateFilteredOutputs();
        };

        target.resetAllOutputProperties = function () {
          resetFilteredOutputs();
        };
      }
    };
  };
});

/*global define: false, d3: false */

define('common/playback-support',['require','common/console','common/performance','common/property-description'],function (require) {
  var console     = require('common/console'),
      performance = require('common/performance'),
      PropertyDescription = require('common/property-description');

  return function PlaybackSupport(args) {
        // DispatchSupport instance or compatible module.
    var dispatch = args && args.dispatch || null,
        // Properties object - it can be used to define 'modelSampleRate'.
        // Instance of PropertiesSupport class is expected.
        propertySupport = args && args.propertySupport || null,
        unitsDefinition = args && args.unitsDefinition || null,
        computeActualUseDuration = args.computeActualUseDuration,
        computeActualDuration    = args.computeActualDuration,

        eventsSupported = (function() {
          // Events support is optional. It should be provided by the
          // DispatchSupport (common/dispatch-support) or another compatible
          // module.
          if (dispatch) {
            if (dispatch.on && dispatch.addEventTypes) {
              dispatch.addEventTypes("play", "stop", "tickStart", "tickEnd");
              return true;
            } else {
              throw new Error("[PlaybackSupport] Provided Dispatch object doesn't implement required interface!");
            }
          } else {
            return false;
          }
        }()),

        stopped = true,
        stopRequest = false,
        restartRequest = false,
        hasPlayed = false;

    /**
      Repeatedly calls `f` at an interval defined by the modelSampleRate property, until f returns
      true. (This is the same signature as d3.timer.)

      If modelSampleRate === 'default', try to run at the "requestAnimationFrame rate"
      (i.e., using d3.timer(), after running f, also request to run f at the next animation frame)

      If modelSampleRate !== 'default', instead uses setInterval to schedule regular calls of f with
      period (1000 / sampleRate) ms, corresponding to sampleRate calls/s
    */
    function timer(f) {
      var intervalID,
          // When target support properties and it defines
          // 'modelSampleRate', it will be used.
          sampleRate = propertySupport && propertySupport.properties.modelSampleRate || 'default';

      if (sampleRate === 'default') {
        // use requestAnimationFrame via d3.timer
        d3.timer(f);
      } else {
        // set an interval to run the model more slowly.
        intervalID = window.setInterval(function() {
          if (f()) {
            window.clearInterval(intervalID);
          }
        }, 1000/sampleRate);
      }
    }

    return {
      mixInto: function(target) {
        var timePropertyDescription;

        if (propertySupport && target.defineOutput) {

          // Define duration-related properties, indicating the time, if any, past which
          // the model should not play.

          // values to show, e.g., as a menu of reasonable choices for requestedDuration
          target.defineOutput('durationOptions', {}, function() {
            // Result is an array of times (in "user-facing" units, such as s in mks models or fs
            // in md2d models), derived from a number of ticks.
            // For mks models having 1/60 seconds per tick, this is 0.5s, 1s, 2s, 5s, 10s, 30s, 60s:
            var TICKS = [30, 60, 120, 300, 600, 1800, 3600];
            var timePerTick = propertySupport.properties.timePerTick;

            if (timePerTick != null) {
              return TICKS.map(function (ticks) { return ticks * timePerTick; });
            }
            return [];
          });

          target.defineOutput('actualUseDuration', {}, computeActualUseDuration);

          target.defineOutput('actualDuration', {
            label: "Experiment duration",
            unitType: 'time',
            format: 'f'
          }, computeActualDuration);
        }

        // All playable (time-based) models should have a method to return time values formatted
        // user consumption. Specific models can override this method; for example md2d internally
        // uses time values in fs but we want to display them as ps.

        if ( ! target.formatTime && unitsDefinition ) {
          timePropertyDescription = new PropertyDescription(unitsDefinition, {
            unitType: 'time',
            // might want to provide a way for models to override this default format without
            // having to override the formatTime method?
            format: '.1f'
          });

          target.formatTime = function(time) {
            return timePropertyDescription.format(time);
          };
        }

        if (typeof target.tick !== "function") {
          target.tick = function () {
            console.warn("[PlaybackSupport] .tick() method should be overwritten by target!");
          };
        }

        target.start = function() {
          // Cleanup stop and restart requests.
          stopRequest = false;
          restartRequest = false;

          if (!stopped) {
            // Do nothing, model is running.
            return target;
          }

          stopped = false;

          timer(function timerTick(elapsedTime) {
            if (eventsSupported) dispatch.tickStart();
            performance.leaveScope("gap");
            // Cancel the timer and refuse to to step the model, if the model is stopped.
            // This is necessary because there is no direct way to cancel a d3 timer.
            // See: https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_timer)
            if (stopRequest) {
              stopped = true;
              if (eventsSupported) dispatch.stop();
              return true;
            }

            if (restartRequest) {
              setTimeout(target.start, 0);
              stopped = true;
              return true;
            }

            if (propertySupport && propertySupport.properties.time >= propertySupport.properties.actualDuration) {
              target.stop();
              return false;
            }

            performance.enterScope("tick");
            target.tick(elapsedTime);
            performance.leaveScope("tick");

            performance.enterScope("gap");
            return false;
          });

          if (eventsSupported) dispatch.play();

          performance.enterScope("gap");
          return target;
        };

        target.restart = function() {
          restartRequest = true;
          return target;
        };

        target.stop = function() {
          stopRequest = true;
          return target;
        };

        target.isStopped = function () {
          return stopped || stopRequest;
        };

        target.on('play.playback-support', function() {
          hasPlayed = true;
        });

        target.on('reset.playback-support', function() {
          hasPlayed = false;
        });

        Object.defineProperty(target, 'hasPlayed', {
          enumerable: true,
          get: function() {
            return hasPlayed;
          }
        });
      }
    };
  };
});

/*global define: false */

define('common/define-builtin-properties',['require','common/validator','common/property-description'],function (require) {

  var validator            = require('common/validator'),
      PropertyDescription  = require('common/property-description');

  return function defineBuiltinProperties(args) {
    var propertySupport   = args.propertySupport,
        metadata          = args.metadata,
        // Optional:
        unitsDefinition   = args.unitsDefinition || {},
        unitsTranslation  = args.unitsTranslation || null,
        setters           = args.setters || {},
        initialProperties = args.initialProperties || null;

    function defineBuiltinProperty(key, type, setter) {
      var metadataForType,
          descriptor,
          propertyChangeInvalidates,
          unitType;

      if (type === 'mainProperty') {
        metadataForType = metadata.mainProperties;
      } else if (type === 'viewOption') {
        metadataForType = metadata.viewOptions;
      } else {
        throw new Error(type + " is not a supported built-in property type");
      }

      propertyChangeInvalidates = validator.propertyChangeInvalidates(metadataForType[key]);

      descriptor = {
        type: type,
        writable: validator.propertyIsWritable(metadataForType[key]),
        set: setter,
        includeInHistoryState: !!metadataForType[key].storeInTickHistory,
        validate: function(value) {
          return validator.validateSingleProperty(metadataForType[key], key, value, false);
        },
        beforeSetCallback: propertyChangeInvalidates ? propertySupport.invalidatingChangePreHook : undefined,
        afterSetCallback: propertyChangeInvalidates ? propertySupport.invalidatingChangePostHook : undefined
      };

      unitType = metadataForType[key].unitType;
      descriptor.description = new PropertyDescription(unitsDefinition, {
          unitType: unitType,
          label: metadataForType[key].label || key
      });

      if (unitType && unitsTranslation) {
        descriptor.beforeSetTransform = function(value) {
          return unitsTranslation.translateToModelUnits(value, unitType);
        };
        descriptor.afterGetTransform = function(value) {
          return unitsTranslation.translateFromModelUnits(value, unitType);
        };
      }

      propertySupport.defineProperty(key, descriptor);
    }

    (function() {
      var mainProperties,
          viewOptions;

      // Define built-in properties using provided metadata.
      Object.keys(metadata.mainProperties).forEach(function (key) {
        defineBuiltinProperty(key, 'mainProperty', setters[key]);
      });
      Object.keys(metadata.viewOptions).forEach(function (key) {
        defineBuiltinProperty(key, 'viewOption', setters[key]);
      });

      if (initialProperties) {
        mainProperties = validator.validateCompleteness(metadata.mainProperties, initialProperties);
        propertySupport.setRawValues(mainProperties);

        viewOptions = validator.validateCompleteness(metadata.viewOptions, initialProperties.viewOptions || {});
        propertySupport.setRawValues(viewOptions);
      }
    }());
  };
});

/*global define: false */

define('common/lab-modeler-mixin',['require','common/property-support','common/parameter-support','common/output-support','common/dispatch-support','common/playback-support','common/define-builtin-properties','common/controllers/export-controller'],function (require) {

  var PropertySupport         = require('common/property-support'),
      ParameterSupport        = require('common/parameter-support'),
      OutputSupport           = require('common/output-support'),
      DispatchSupport         = require('common/dispatch-support'),
      PlaybackSupport         = require('common/playback-support'),
      defineBuiltinProperties = require('common/define-builtin-properties'),
      // it's somewhat unfortunate to have an "upwards" dependency on the controller layer
      ExportController    = require('common/controllers/export-controller');

  return function LabModelerMixin(args) {

    var api,

        /**
         * Accepted arguments:
         */
        metadata          = args.metadata,
        setters           = args.setters,
        unitsDefinition   = args.unitsDefinition,
        unitsTranslation  = args.unitsTranslation,
        initialProperties = args.initialProperties,
        tickHistory       = args.tickHistory,

        // defaults to true:
        usePlaybackSupport = args.usePlaybackSupport === undefined || !!args.usePlaybackSupport,

        propertySupport = new PropertySupport({
          types: ["output", "parameter", "mainProperty", "viewOption"]
        }),
        parameterSupport = new ParameterSupport({
          propertySupport: propertySupport,
          unitsDefinition: unitsDefinition
        }),
        outputSupport = new OutputSupport({
          propertySupport: propertySupport,
          unitsDefinition: unitsDefinition,
          tickHistory: tickHistory
        }),
        dispatchSupport = new DispatchSupport(),
        playbackSupport;

        // Make these functions available to any model that chooses to implement duration
        // related properties. PlaybackSupport mixes the duration-related property definitions
        // into models that use it, but other model types, such as sensor models, have to use
        // alternate definitions. The functions below implement some of the logic the different
        // implementations have in common.
        function computeActualDuration() {
          // changing requestedDuration will have an effect iff actualUseDuration == true
          var actualUseDuration = propertySupport.properties.actualUseDuration;
          var requestedDuration = propertySupport.properties.requestedDuration;
          var durationOptions;

          if ( ! actualUseDuration ) {
            return Infinity;
          }

          if (requestedDuration != null) {
            return requestedDuration;
          }

          // need to use a default
          durationOptions = propertySupport.properties.durationOptions;

          if (durationOptions.length > 0) {
            return durationOptions[Math.floor(durationOptions.length / 2)];
          } else {
            // No good default; punt. Leave actualUseDuration = true, but don't actually stop.
            return Infinity;
          }
        }

        function computeActualUseDuration() {
          var useDuration = propertySupport.properties.useDuration;
          return useDuration === true || useDuration === 'codap' && ExportController.canExportData();
        }

        if (usePlaybackSupport) {
          playbackSupport = new PlaybackSupport({
            dispatch: dispatchSupport,
            propertySupport: propertySupport,
            unitsDefinition: unitsDefinition,
            computeActualDuration: computeActualDuration,
            computeActualUseDuration: computeActualUseDuration
          });
        }

    // Is the model setup complete, so the model is ready to be played and record to tick history?
    // "Naked" models are ready upon instantation, but in an interactive, a model shouldn't record
    // history or play until external code has finished setting up parameters, running onload
    // scripts; in the latter case, we wait for a call to model.ready()
    var isReady = false;

    // FIXME: These events have to be available as some other modules try to
    // add listeners. Probably they aren't necessary, trace it and fix.
    dispatchSupport.addEventTypes("reset", "stepForward", "stepBack", "seek", "invalidation", "willReset", "ready", "log");

    api = {
      mixInto: function(target) {
        propertySupport.mixInto(target);
        parameterSupport.mixInto(target);
        outputSupport.mixInto(target);
        dispatchSupport.mixInto(target);

        if (playbackSupport) {
          playbackSupport.mixInto(target);
        }

        // This allows external code (such as the model controller) to trigger the model's
        // willReset event. This allows observers such as the export controller to observe model
        // state before it gets blown away.
        target.willReset = function() {
          dispatchSupport.willReset();
        };

        /**
          Indicate that the model is ready, causing it to save its current state as the initial state,
          to push the current state as the first state of the regular tick history, and to emit the
          ready event.

          This is a public method because we may have to wait for
          external code to indicate that setup is complete (e.g., code that defines parameters and sets
          their initial values, or manipulates the model in some other way)

          This method is called upon model instantiation if initializationOptions.waitForSetup is false
          Otherwise, it must be called by external code. It is an error to call it twice.
        */
        target.ready = function() {
          if (isReady) {
            throw new Error("ready() called on an already-ready model.");
          }

          if (api.tickHistory) {
            api.tickHistory.saveInitialState();
            api.tickHistory.push();
          }

          propertySupport.invalidatingChangePreHook();
          isReady = true;
          propertySupport.invalidatingChangePostHook();

          dispatchSupport.ready();
        };

        Object.defineProperty(target, 'isReady', {
          enumerable: true,
          get: function() {
            return isReady;
          }
        });

        if (metadata) {
          defineBuiltinProperties({
            propertySupport: propertySupport,
            metadata: metadata,

            unitsDefinition: unitsDefinition,
            unitsTranslation: unitsTranslation,
            setters: setters,
            initialProperties: initialProperties
          });
        }
      },

      get propertySupport() {
        return propertySupport;
      },

      get parameterSupport() {
        return parameterSupport;
      },

      get outputSupport() {
        return outputSupport;
      },

      get dispatchSupport() {
        return dispatchSupport;
      },

      get playbackSupport() {
        return playbackSupport;
      },

      get computeActualDuration() {
        return computeActualDuration;
      },

      get computeActualUseDuration() {
        return computeActualUseDuration;
      }
    };

    return api;
  };
});

/*global define: false */

define('common/models/tick-history',[],function() {


  /**
    Class which handles tick history. It supports saving and restoring state
    of core state objects defined by the modeler and engine. However, while
    adding a new object which should also be saved in tick history, consider
    utilization of "external objects" - this is special object which should
    implement TickHistoryCompatible Interface:
      #setHistoryLength(number)
      #push()
      #extract(index)
      #invalidate(index)

      Note that index argument is *always* limited to [0, historyLength) range.

    "External objects" handle changes of the current step itself. TickHistory
    only sends requests to perform various operations. To register new
    external object use #registerExternalObject(object) method.

    It allows to decentralize management of tick history and tight coupling
    TickTistory with API of various objects.
  */
  return function TickHistory(modelState, model, size) {
    var tickHistory = {},
        initialState,
        list,
        listState,
        defaultSize = 1000,
        // List of objects defining TickHistoryCompatible Interface.
        externalObjects = [],

        // Provide the "old" interface for models that don't use PropertySupport yet, but provide
        // a different, new interface for models using PropertySupport for their parameters, etc.
        // Such models are smart enough to send a single hash of raw property values for all the
        // properties (parameters, main properties, view properties, etc) we need to save. Older
        // models need to provide us with separate lists of "regular" properties and parameters,
        // with their own separate restore callbacks.
        //
        //     ***      Remember to remove this when all models use PropertySupport!        ***
        //
        useNewInterface = !!modelState.getProperties;

    function newState() {
      return { input: {}, state: [], parameters: {} };
    }

    function reset() {
      var i;

      list = [];
      listState = {
        // Equal to list.length:
        length: 0,
        // Drop oldest state in order to keep list no longer than this:
        maxSize: size,
        // Index into `list` of the current state:
        index: -1,
        // Total length of "total history" (counting valid history states that have been dropped)
        counter: -1,
        // Index in "total history" of the oldest state in the list.
        // Invariant: counter == index + startCounter
        startCounter: 0
      };
      // Send push request to external objects defining TickHistoryCompatible Interface.
      for (i = 0; i < externalObjects.length; i++) {
        if (externalObjects[i].reset) {
          externalObjects[i].reset();
        }
      }
    }

    function copyModelState(destination) {
      var i,
          prop,
          state,
          parameters,
          name;

      if (useNewInterface) {
        // we expect that modelState.getProperties returns us a hash we can keep
        destination.input = modelState.getProperties();
      } else {
        // save model input properties
        for (i = 0; i < modelState.input.length; i++) {
          prop = modelState.input[i];
          destination.input[prop] = modelState.getRawPropertyValue(prop);
        }

        // save model parameters
        parameters = modelState.parameters;
        for (name in parameters) {
          if (parameters.hasOwnProperty(name) && parameters[name].isDefined) {
            destination.parameters[name] = modelState.getRawPropertyValue(name);
          }
        }
      }

      // save model objects defining state
      state = modelState.state;
      for (i = 0; i < state.length; i++) {
        destination.state[i] = state[i].clone();
      }
    }

    /** Copy the current model state into the list at list[listState.index+1] and updates listState.
        Removes any (now-invalid) states in the list that come after the newly pushed state.
    */
    function push() {
      var lastState = newState(),
          i;

      copyModelState(lastState);
      list[listState.index+1] = lastState;

      // Drop the oldest state if we went over the max list size
      if (list.length > listState.maxSize) {
        list.splice(0,1);
        listState.startCounter++;
      } else {
        listState.index++;
      }
      listState.counter = listState.index + listState.startCounter;

      // Send push request to external objects defining TickHistoryCompatible Interface.
      for (i = 0; i < externalObjects.length; i++) {
        externalObjects[i].push();
      }

      invalidateFollowingState();
      listState.length = list.length;
    }

    /** Invalidate (remove) all history after current index. For example, after seeking backwards
        and then pushing new state */
    function invalidateFollowingState() {
      var i;

      list.length = listState.index+1;
      listState.length = list.length;

      // Invalidate external objects defining TickHistoryCompatible Interface.
      for (i = 0; i < externalObjects.length; i++) {
        externalObjects[i].invalidate(listState.index);
      }
    }

    function extract(savedState) {
      var i,
          state;

      // restore model input properties
      modelState.restoreProperties(savedState.input);

      if (!useNewInterface) {
        // old interface requires restoring parameters separately
        modelState.restoreParameters(savedState.parameters);
      }

      // restore model objects defining state
      state = savedState.state;
      for (i = 0; i < state.length; i++) {
        modelState.state[i].restore(state[i]);
      }

      // Send extract request to external objects defining TickHistoryCompatible Interface.
      for (i = 0; i < externalObjects.length; i++) {
        externalObjects[i].extract(listState.index);
      }
    }

    function checkIndexArg(index) {
      if (index < 0) {
        throw new Error("TickHistory: extract index [" + index + "] less than 0");
      }
      if (index >= list.length) {
        throw new Error("TickHistory: extract index [" + index + "] greater than list.length: " + list.length);
      }
      return index;
    }

    //
    // Public methods
    //
    tickHistory.isEmpty = function() {
      return listState.index === 0;
    };

    tickHistory.push = function() {
      push();
    };

    tickHistory.returnTick = function(ptr) {
      var i;
      if (typeof ptr === 'number') {
        i = ptr;
      } else {
        i = listState.index;
      }
      checkIndexArg(i);
      return list[i];
    };

    tickHistory.extract = function(ptr) {
      var i;
      if (typeof ptr === 'number') {
        i = ptr;
      } else {
        i = listState.index;
      }
      checkIndexArg(i);
      extract(list[i]);
    };

    tickHistory.saveInitialState = function() {
      initialState = newState();
      copyModelState(initialState);
    };

    tickHistory.restoreInitialState = function() {
      reset();
      extract(initialState);
      push();
    };

    tickHistory.reset = reset;

    tickHistory.decrementExtract = function() {
      if (listState.counter > listState.startCounter) {
        listState.index--;
        listState.counter--;
        extract(list[listState.index]);
      }
    };

    tickHistory.incrementExtract = function() {
      listState.index++;
      listState.counter++;
      extract(list[listState.index]);
    };

    tickHistory.seekExtract = function(ptr) {
      if (ptr < listState.startCounter) ptr = listState.startCounter;
      if (ptr >= listState.startCounter + listState.length) ptr = listState.startCounter + listState.length - 1;
      listState.counter = ptr;
      listState.index = ptr - listState.startCounter;
      extract(list[listState.index]);
    };

    tickHistory.invalidateFollowingState = invalidateFollowingState;

    tickHistory.get = function(key) {
      return listState[key];
    };

    tickHistory.set = function(key, val) {
      return listState[key] = val;
    };

    /**
      Registers a new external object. It is a special object, which handles changes of step itself.
      TickHistory object only sends requests for various actions.
      External object should implement TickHistoryCompatible Interface:
        #setHistoryLength(number)
        #push()
        #extract(index)
        #invalidate(index)
    */
    tickHistory.registerExternalObject = function (externalObj) {
      externalObj.setHistoryLength(listState.maxSize);
      externalObjects.push(externalObj);
    };

    //
    // Initialization
    //
    if (size == null) size = defaultSize;

    reset();
    return tickHistory;
  };
});

/*global define: false */
define('models/md2d/models/aminoacids-props',[],function() {
  return [
    {
      "fullName": "Alanine",
      "abbreviation": "Ala",
      "symbol": "A",
      "molWeight": 89.09,
      "charge": 0,
      "hydrophobicityRB": 2.15,
      "pK": 0,
      "surface": 115,
      "volume": 88.6,
      "solubility": 16.65,
      "hydrophobicity": 1,
      "property": "Total aliphatic; hydrophobic"
    },
    {
      "fullName": "Arginine",
      "abbreviation": "Arg",
      "symbol": "R",
      "molWeight": 174.2,
      "charge": 1,
      "hydrophobicityRB": 2.23,
      "pK": 12,
      "surface": 225,
      "volume": 173.4,
      "solubility": 15,
      "hydrophobicity": -2,
      "property": "Acidic side chains; strongly polar; cationic"
    },
    {
      "fullName": "Asparagine",
      "abbreviation": "Asn",
      "symbol": "N",
      "molWeight": 132.12,
      "charge": 0,
      "hydrophobicityRB": 1.05,
      "pK": 0,
      "surface": 160,
      "volume": 114.1,
      "solubility": 3.53,
      "hydrophobicity": -1,
      "property": "Strongly polar"
    },
    {
      "fullName": "Asparticacid",
      "abbreviation": "Asp",
      "symbol": "D",
      "molWeight": 133.1,
      "charge": -1,
      "hydrophobicityRB": 1.13,
      "pK": 4.4,
      "surface": 150,
      "volume": 111.1,
      "solubility": 0.778,
      "hydrophobicity": -2,
      "property": "Acidic side chains; strongly polar; anionic"
    },
    {
      "fullName": "Cysteine",
      "abbreviation": "Cys",
      "symbol": "C",
      "molWeight": 121.15,
      "charge": 0,
      "hydrophobicityRB": 1.2,
      "pK": 8.5,
      "surface": 135,
      "volume": 108.5,
      "solubility": 1000,
      "hydrophobicity": 1,
      "property": "Polar side chains; semipolar"
    },
    {
      "fullName": "Glutamine",
      "abbreviation": "Gln",
      "symbol": "Q",
      "molWeight": 146.15,
      "charge": 0,
      "hydrophobicityRB": 1.65,
      "pK": 0,
      "surface": 180,
      "volume": 143.8,
      "solubility": 2.5,
      "hydrophobicity": -1,
      "property": "Strongly polar"
    },
    {
      "fullName": "Glutamicacid",
      "abbreviation": "Glu",
      "symbol": "E",
      "molWeight": 147.13,
      "charge": -1,
      "hydrophobicityRB": 1.73,
      "pK": 4.4,
      "surface": 190,
      "volume": 138.4,
      "solubility": 0.864,
      "hydrophobicity": -2,
      "property": "Acidic side chains; strongly polar; anionic"
    },
    {
      "fullName": "Glycine",
      "abbreviation": "Gly",
      "symbol": "G",
      "molWeight": 75.07,
      "charge": 0,
      "hydrophobicityRB": 1.18,
      "pK": 0,
      "surface": 75,
      "volume": 60.1,
      "solubility": 24.99,
      "hydrophobicity": 1,
      "property": "Semipolar"
    },
    {
      "fullName": "Histidine",
      "abbreviation": "His",
      "symbol": "H",
      "molWeight": 155.16,
      "charge": 1,
      "hydrophobicityRB": 2.45,
      "pK": 6.5,
      "surface": 195,
      "volume": 153.2,
      "solubility": 4.19,
      "hydrophobicity": -2,
      "property": "Basic side chains; strongly polar; cationic"
    },
    {
      "fullName": "Isoleucine",
      "abbreviation": "Ile",
      "symbol": "I",
      "molWeight": 131.17,
      "charge": 0,
      "hydrophobicityRB": 3.88,
      "pK": 0,
      "surface": 175,
      "volume": 166.7,
      "solubility": 4.117,
      "hydrophobicity": 1,
      "property": "Branched chain aliphatic; hydrophobic"
    },
    {
      "fullName": "Leucine",
      "abbreviation": "Leu",
      "symbol": "L",
      "molWeight": 131.17,
      "charge": 0,
      "hydrophobicityRB": 4.1,
      "pK": 10,
      "surface": 170,
      "volume": 166.7,
      "solubility": 2.426,
      "hydrophobicity": 1,
      "property": "Branched chain aliphatic; hydrophobic"
    },
    {
      "fullName": "Lysine",
      "abbreviation": "Lys",
      "symbol": "K",
      "molWeight": 146.19,
      "charge": 1,
      "hydrophobicityRB": 3.05,
      "pK": 0,
      "surface": 200,
      "volume": 168.6,
      "solubility": 1000,
      "hydrophobicity": -2,
      "property": "Acidic side chains; strongly polar; cationic"
    },
    {
      "fullName": "Methionine",
      "abbreviation": "Met",
      "symbol": "M",
      "molWeight": 149.21,
      "charge": 0,
      "hydrophobicityRB": 3.43,
      "pK": 0,
      "surface": 185,
      "volume": 162.9,
      "solubility": 3.81,
      "hydrophobicity": 1,
      "property": "Totally alyphatic"
    },
    {
      "fullName": "Phenylalanine",
      "abbreviation": "Phe",
      "symbol": "F",
      "molWeight": 165.19,
      "charge": 0,
      "hydrophobicityRB": 3.46,
      "pK": 0,
      "surface": 210,
      "volume": 189.9,
      "solubility": 2.965,
      "hydrophobicity": 2,
      "property": "Totally aromatic"
    },
    {
      "fullName": "Proline",
      "abbreviation": "Pro",
      "symbol": "P",
      "molWeight": 115.13,
      "charge": 0,
      "hydrophobicityRB": 3.1,
      "pK": 0,
      "surface": 145,
      "volume": 112.7,
      "solubility": 162.3,
      "hydrophobicity": 1,
      "property": "Totally alyphatic"
    },
    {
      "fullName": "Serine",
      "abbreviation": "Ser",
      "symbol": "S",
      "molWeight": 105.09,
      "charge": 0,
      "hydrophobicityRB": 1.4,
      "pK": 0,
      "surface": 115,
      "volume": 89,
      "solubility": 5.023,
      "hydrophobicity": -1,
      "property": "Semipolar"
    },
    {
      "fullName": "Threonine",
      "abbreviation": "Thr",
      "symbol": "T",
      "molWeight": 119.12,
      "charge": 0,
      "hydrophobicityRB": 2.25,
      "pK": 0,
      "surface": 140,
      "volume": 116.1,
      "solubility": 1000,
      "hydrophobicity": -1,
      "property": "Semipolar"
    },
    {
      "fullName": "Tryptophan",
      "abbreviation": "Trp",
      "symbol": "W",
      "molWeight": 204.23,
      "charge": 0,
      "hydrophobicityRB": 4.11,
      "pK": 0,
      "surface": 255,
      "volume": 227.8,
      "solubility": 1.136,
      "hydrophobicity": 2,
      "property": "Totally aromatic"
    },
    {
      "fullName": "Tyrosine",
      "abbreviation": "Tyr",
      "symbol": "Y",
      "molWeight": 181.19,
      "charge": 0,
      "hydrophobicityRB": 2.81,
      "pK": 10,
      "surface": 230,
      "volume": 193.6,
      "solubility": 0.045,
      "hydrophobicity": 1,
      "property": "Hydrophobic; total aromatic"
    },
    {
      "fullName": "Valine",
      "abbreviation": "Val",
      "symbol": "V",
      "molWeight": 117.15,
      "charge": 0,
      "hydrophobicityRB": 3.38,
      "pK": 0,
      "surface": 155,
      "volume": 140,
      "solubility": 8.85,
      "hydrophobicity": 1,
      "property": "Branched chain aliphatic; hydrophobic"
    }
  ];
});



/*
Module which provides convenience functions related to amino acids.
*/


(function() {

  define('cs!models/md2d/models/aminoacids-helper',['require','models/md2d/models/aminoacids-props'],function(require) {
    var FIST_ELEMENT_ID, RNA_CODON_TABLE, aminoacidsProps;
    aminoacidsProps = require('models/md2d/models/aminoacids-props');
    FIST_ELEMENT_ID = 5;
    RNA_CODON_TABLE = {
      "UUU": "Phe",
      "UUC": "Phe",
      "UUA": "Leu",
      "UUG": "Leu",
      "CUU": "Leu",
      "CUC": "Leu",
      "CUA": "Leu",
      "CUG": "Leu",
      "AUU": "Ile",
      "AUC": "Ile",
      "AUA": "Ile",
      "AUG": "Met",
      "GUU": "Val",
      "GUC": "Val",
      "GUA": "Val",
      "GUG": "Val",
      "UCU": "Ser",
      "UCC": "Ser",
      "UCA": "Ser",
      "UCG": "Ser",
      "AGU": "Ser",
      "AGC": "Ser",
      "CCU": "Pro",
      "CCC": "Pro",
      "CCA": "Pro",
      "CCG": "Pro",
      "ACU": "Thr",
      "ACC": "Thr",
      "ACA": "Thr",
      "ACG": "Thr",
      "GCU": "Ala",
      "GCC": "Ala",
      "GCA": "Ala",
      "GCG": "Ala",
      "UAU": "Tyr",
      "UAC": "Tyr",
      "CAU": "His",
      "CAC": "His",
      "CAA": "Gln",
      "CAG": "Gln",
      "AAU": "Asn",
      "AAC": "Asn",
      "AAA": "Lys",
      "AAG": "Lys",
      "GAU": "Asp",
      "GAC": "Asp",
      "GAA": "Glu",
      "GAG": "Glu",
      "UGU": "Cys",
      "UGC": "Cys",
      "UGG": "Trp",
      "CGU": "Arg",
      "CGC": "Arg",
      "CGA": "Arg",
      "CGG": "Arg",
      "AGA": "Arg",
      "AGG": "Arg",
      "GGU": "Gly",
      "GGC": "Gly",
      "GGA": "Gly",
      "GGG": "Gly",
      "UAA": "STOP",
      "UAG": "STOP",
      "UGA": "STOP"
    };
    return {
      /*
        ID of an element representing the first amino acid in the elements collection.
      */

      firstElementID: FIST_ELEMENT_ID,
      /*
        ID of an element representing the last amino acid in the elements collection.
      */

      lastElementID: FIST_ELEMENT_ID + aminoacidsProps.length - 1,
      /*
        Element ID of the cysteine amino acid.
        Note that it should be stored in this class (instead of hard-coded in the engine),
        as it can be changed in the future.
      */

      cysteineElement: 9,
      /*
        Converts @abbreviation of amino acid to element ID.
      */

      abbrToElement: function(abbreviation) {
        var aminoacid, i, _i, _len;
        for (i = _i = 0, _len = aminoacidsProps.length; _i < _len; i = ++_i) {
          aminoacid = aminoacidsProps[i];
          if (aminoacid.abbreviation === abbreviation) {
            return i + this.firstElementID;
          }
        }
      },
      /*
        Returns properties (hash) of amino acid which is represented by a given @elementID.
      */

      getAminoAcidByElement: function(elementID) {
        return aminoacidsProps[elementID - this.firstElementID];
      },
      /*
        Checks if given @elementID represents amino acid.
      */

      isAminoAcid: function(elementID) {
        return elementID >= this.firstElementID && elementID <= this.lastElementID;
      },
      /*
        Returns polar amino acids (array of their element IDs).
      */

      getPolarAminoAcids: function() {
        var abbr, _i, _len, _ref, _results;
        _ref = ["Asn", "Gln", "Ser", "Thr"];
        _results = [];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          abbr = _ref[_i];
          _results.push(this.abbrToElement(abbr));
        }
        return _results;
      },
      /*
        Converts RNA Codon to amino acid abbreviation
      */

      codonToAbbr: function(codon) {
        if (codon.length !== 3) {
          return "STOP";
        } else {
          return RNA_CODON_TABLE[codon];
        }
      }
    };
  });

}).call(this);

/*global define: true */
/*jslint eqnull: true */

// Simple (Box-Muller) univariate-normal random number generator.
//
// The 'science.js' library includes a Box-Muller implementation which is likely to be slower, especially in a
// modern Javascript engine, because it uses a rejection method to pick the random point in the unit circle.
// See discussion on pp. 1-3 of:
// http://www.math.nyu.edu/faculty/goodman/teaching/MonteCarlo2005/notes/GaussianSampling.pdf
//

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. RequireJS Optimizer will strip out this if statement.


define('models/md2d/models/engine/math/distributions',['require','exports','module'],function (require, exports, module) {

  exports.normal = (function() {
    var next = null;

    return function(mean, sd) {
      if (mean == null) mean = 0;
      if (sd == null)   sd = 1;

      var r, ret, theta, u1, u2;

      if (next) {
        ret  = next;
        next = null;
        return ret;
      }

      u1    = Math.random();
      u2    = Math.random();
      theta = 2 * Math.PI * u1;
      r     = Math.sqrt(-2 * Math.log(u2));

      next = mean + sd * (r * Math.sin(theta));
      return mean + sd * (r * Math.cos(theta));
    };
  }());
});

/*global define: true */
/*jslint eqnull: true */

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. RequireJS Optimizer will strip out this if statement.


define('models/md2d/models/engine/math/utils',['require','exports','module'],function (require, exports, module) {

  /**
    Returns a function which accepts a single numeric argument and returns:

     * the arithmetic mean of the windowSize most recent inputs, including the current input
     * NaN if there have not been windowSize inputs yet.

    The default windowSize is 1000.

  */
  exports.getWindowedAverager = function(windowSize) {

    if (windowSize == null) windowSize = 1000;      // default window size

    var i = 0,
        vals = [],
        sum_vals = 0;

    return function(val) {
      sum_vals -= (vals[i] || 0);
      sum_vals += val;
      vals[i] = val;

      if (++i === windowSize) i = 0;

      if (vals.length === windowSize) {
        return sum_vals / windowSize;
      }
      else {
        // don't allow any numerical comparisons with result to be true
        return NaN;
      }
    };
  };

  /**
    Returns angle in radians between vectors AC and BC.
    Point A is defined by (xa, ya), B by (xb, yb) and C by (xc, yc) arguments.
   */
  exports.getAngleBetweenVec = function(xa, ya, xb, yb, xc, yc) {
    var dxij = xa - xc;
    var dxkj = xb - xc;
    var dyij = ya - yc;
    var dykj = yb - yc;
    var rijSquared = dxij * dxij + dyij * dyij;
    var rkjSquared = dxkj * dxkj + dykj * dykj;
    var rij = Math.sqrt(rijSquared);
    var rkj = Math.sqrt(rkjSquared);
    // Calculate cos using dot product definition.
    var cosTheta = (dxij * dxkj + dyij * dykj) / (rij * rkj);
    if (cosTheta > 1.0) cosTheta = 1.0;
    else if (cosTheta < -1.0) cosTheta = -1.0;

    return Math.acos(cosTheta);
  };
});

/*global define: true */
/*jshint eqnull:true */
/**
  Simple, good-enough minimization via gradient descent.
*/

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. RequireJS Optimizer will strip out this if statement.


define('models/md2d/models/engine/math/minimizer',['require','exports','module','common/console'],function (require, exports, module) {
  // Dependencies.
  var console = require('common/console');

  exports.minimize = function(f, x0, opts) {
    opts = opts || {};

    if (opts.precision == null) opts.precision = 0.01;

    var // stop when the absolute difference between successive values of f is this much or less
        precision = opts.precision,

        // array of [min, max] boundaries for each component of x
        bounds    = opts.bounds,

        // maximum number of iterations
        maxiter   = opts.maxiter   || 1000,

        // optionally, stop when f is less than or equal to this value
        stopval   = opts.stopval   || -Infinity,

        // maximum distance to move x between steps
        maxstep   = opts.maxstep   || 0.01,

        // multiplied by the gradient
        eps       = opts.eps       || 0.01,
        dim       = x0.length,
        x,
        res,
        f_cur,
        f_prev,
        grad,
        maxstepsq,
        gradnormsq,
        iter,
        i,
        a;

    maxstepsq = maxstep*maxstep;

    // copy x0 into x (which we will mutate)
    x = [];
    for (i = 0; i < dim; i++) {
      x[i] = x0[i];
    }

    // evaluate f and get the gradient
    res = f.apply(null, x);
    f_cur = res[0];
    grad = res[1];

    iter = 0;
    do {
      if (f_cur <= stopval) {
        break;
      }

      if (iter > maxiter) {
        console.log("maxiter reached");
        // don't throw on error, but return some diagnostic information
        return { error: "maxiter reached", f: f_cur, iter: maxiter, x: x };
      }

      // Limit gradient descent step size to maxstep
      gradnormsq = 0;
      for (i = 0; i < dim; i++) {
        gradnormsq += grad[i]*grad[i];
      }
      if (eps*eps*gradnormsq > maxstepsq) {
        a = Math.sqrt(maxstepsq / gradnormsq) / eps;
        for (i = 0; i < dim; i++) {
          grad[i] = a * grad[i];
        }
      }

      // Take a step in the direction opposite the gradient
      for (i = 0; i < dim; i++) {
        x[i] -= eps * grad[i];

        // check bounds
        if (bounds && x[i] < bounds[i][0]) {
          x[i] = bounds[i][0];
        }
        if (bounds && x[i] > bounds[i][1]) {
          x[i] = bounds[i][1];
        }
      }

      f_prev = f_cur;

      res = f.apply(null, x);
      f_cur = res[0];
      grad = res[1];

      iter++;
    } while ( Math.abs(f_cur-f_prev) > precision );

    return [f_cur, x];
  };
});

/*global define: true */

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. RequireJS Optimizer will strip out this if statement.


define('models/md2d/models/engine/math/index',['require','exports','module','./distributions','./utils','./utils','./minimizer'],function (require, exports, module) {
  exports.normal              = require('./distributions').normal;
  exports.getWindowedAverager = require('./utils').getWindowedAverager;
  exports.getAngleBetweenVec  = require('./utils').getAngleBetweenVec;
  exports.minimize            = require('./minimizer').minimize;
});

/*global define: true */

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. RequireJS Optimizer will strip out this if statement.


define('models/md2d/models/engine/potentials/coulomb',['require','exports','module','../constants/index'],function (require, exports, module) {

  var
  constants = require('../constants/index'),
  unit      = constants.unit,

  // Classic MW uses a value for Coulomb's constant that is effectively 0.346 of the real value
  CLASSIC_MW_FUDGE_FACTOR = 0.346,

  COULOMB_CONSTANT_IN_METERS_PER_FARAD = constants.COULOMB_CONSTANT.as( constants.unit.METERS_PER_FARAD ),

  NANOMETERS_PER_METER = constants.ratio(unit.NANOMETER, { per: unit.METER }),
  COULOMBS_SQ_PER_ELEMENTARY_CHARGE_SQ = Math.pow( constants.ratio(unit.COULOMB, { per: unit.ELEMENTARY_CHARGE }), 2),

  EV_PER_JOULE = constants.ratio(unit.EV, { per: unit.JOULE }),
  MW_FORCE_UNITS_PER_NEWTON = constants.ratio(unit.MW_FORCE_UNIT, { per: unit.NEWTON }),

  // Coulomb constant for expressing potential in eV given elementary charges, nanometers
  k_ePotential = CLASSIC_MW_FUDGE_FACTOR *
                 COULOMB_CONSTANT_IN_METERS_PER_FARAD *
                 COULOMBS_SQ_PER_ELEMENTARY_CHARGE_SQ *
                 NANOMETERS_PER_METER *
                 EV_PER_JOULE,

  // Coulomb constant for expressing force in Dalton*nm/fs^2 given elementary charges, nanometers
  k_eForce = CLASSIC_MW_FUDGE_FACTOR *
             COULOMB_CONSTANT_IN_METERS_PER_FARAD *
             COULOMBS_SQ_PER_ELEMENTARY_CHARGE_SQ *
             NANOMETERS_PER_METER *
             NANOMETERS_PER_METER *
             MW_FORCE_UNITS_PER_NEWTON,

  // Exports

  /** Input:
       r: distance in nanometers,
       q1, q2: elementary charges,
       dC: dielectric constant, unitless,
       rDE: realistic dielectric effect switch, boolean.

      Output units: eV
  */
  potential = exports.potential = function(r, q1, q2, dC, rDE) {
    if (rDE && dC > 1 && r < 1.2) {
      // "Realistic Dielectric Effect" mode:
      // Diminish dielectric constant value using distance between particles.
      // Function based on: http://en.wikipedia.org/wiki/Generalized_logistic_curve
      // See plot for dC = 80: http://goo.gl/7zU6a
      // For optimization purposes it returns asymptotic value when r > 1.2.
      dC = 1 + (dC - 1)/(1 + Math.exp(-12 * r + 7));
    }
    return k_ePotential * ((q1 * q2) / r) / dC;
  },


  /** Input:
       rSq: squared distance in nanometers^2,
       q1, q2: elementary charges,
       dC: dielectric constant, unitless,
       rDE: realistic dielectric effect switch, boolean.

      Output units: "MW Force Units" (Dalton * nm / fs^2)
  */
  forceFromSquaredDistance = exports.forceFromSquaredDistance = function(rSq, q1, q2, dC, rDE) {
    var r = Math.sqrt(rSq);
    if (rDE && dC > 1 && r < 1.2) {
      // "Realistic Dielectric Effect" mode:
      // Diminish dielectric constant value using distance between particles.
      // Function based on: http://en.wikipedia.org/wiki/Generalized_logistic_curve
      // See plot for dC = 80: http://goo.gl/7zU6a
      // For optimization purposes it returns asymptotic value when r > 1.2.
      dC = 1 + (dC - 1)/(1 + Math.exp(-12 * r + 7));
    }
    return -k_eForce * ((q1 * q2) / rSq) / dC;
  },


  forceOverDistanceFromSquaredDistance = exports.forceOverDistanceFromSquaredDistance = function(rSq, q1, q2, dC, rDE) {
    return forceFromSquaredDistance(rSq, q1, q2, dC, rDE) / Math.sqrt(rSq);
  },

  /** Input:
       r: distance in nanometers,
       q1, q2: elementary charges,
       dC: dielectric constant, unitless,
       rDE: realistic dielectric effect switch, boolean.

      Output units: "MW Force Units" (Dalton * nm / fs^2)
  */
  force = exports.force = function(r, q1, q2, dC, rDE) {
    return forceFromSquaredDistance(r*r, q1, q2, dC, rDE);
  };
});

/*global define: true */
/*jshint eqnull:true boss:true */

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. RequireJS Optimizer will strip out this if statement.


define('models/md2d/models/engine/potentials/lennard-jones',['require','exports','module','../constants/index'],function (require, exports, module) {

  var constants = require('../constants/index'),
      unit      = constants.unit,

      NANOMETERS_PER_METER = constants.ratio( unit.NANOMETER, { per: unit.METER }),
      MW_FORCE_UNITS_PER_NEWTON = constants.ratio( unit.MW_FORCE_UNIT, { per: unit.NEWTON });

  /**
    Helper function that returns the correct pairwise epsilon value to be used
    when elements each have epsilon values epsilon1, epsilon2
  */
  exports.pairwiseEpsilon = function(epsilon1, epsilon2) {
    return 0.5 * (epsilon1 + epsilon2);
  },

  /**
    Helper function that returns the correct pairwise sigma value to be used
    when elements each have sigma values sigma1, sigma2
  */
  exports.pairwiseSigma = function(sigma1, sigma2) {
    return Math.sqrt(sigma1 * sigma2);
  },

  /**
    Helper function that returns the correct rmin value for a given sigma
  */
  exports.rmin = function(sigma) {
    return Math.pow(2, 1/6) * sigma;
  };

  /**
    Helper function that returns the correct atomic radius for a given sigma
  */
  exports.radius = function(sigma) {
    // See line 637 of Atom.java (org.concord.mw2d.models.Atom)
    // This assumes the "VdW percentage" is 100%. In classic MW the VdW percentage is settable.
    return 0.5 * sigma;
  };

  /**
    Returns a new object with methods for calculating the force and potential for a Lennard-Jones
    potential with particular values of its parameters epsilon and sigma. These can be adjusted.

    To avoid the needing to take square roots during calculation of pairwise forces, there are
    also methods which calculate the inter-particle potential directly from a squared distance, and
    which calculate the quantity (force/distance) directly from a squared distance.

    This function also accepts a callback function which will be called with a hash representing
    the new coefficients, whenever the LJ coefficients are changed for the returned calculator.
  */
  exports.newLJCalculator = function(params, cb) {

    var epsilon,          // parameter; depth of the potential well, in eV
        sigma,            // parameter: characteristic distance from particle, in nm

        rmin,             // distance from particle at which the potential is at its minimum
        alpha_Potential,  // precalculated; units are eV * nm^12
        beta_Potential,   // precalculated; units are eV * nm^6
        alpha_Force,      // units are "MW Force Units" * nm^13
        beta_Force,       // units are "MW Force Units" * nm^7

        initialized = false, // skip callback during initialization

        setCoefficients = function(e, s) {
          // Input units:
          //  epsilon: eV
          //  sigma:   nm

          epsilon = e;
          sigma   = s;
          rmin    = exports.rmin(sigma);

          if (epsilon != null && sigma != null) {
            alpha_Potential = 4 * epsilon * Math.pow(sigma, 12);
            beta_Potential  = 4 * epsilon * Math.pow(sigma, 6);

            // (1 J * nm^12) = (1 N * m * nm^12)
            // (1 N * m * nm^12) * (b nm / m) * (c MWUnits / N) = (abc MWUnits nm^13)
            alpha_Force = 12 * constants.convert(alpha_Potential, { from: unit.EV, to: unit.JOULE }) * NANOMETERS_PER_METER * MW_FORCE_UNITS_PER_NEWTON;
            beta_Force =  6 * constants.convert(beta_Potential,  { from: unit.EV, to: unit.JOULE }) * NANOMETERS_PER_METER * MW_FORCE_UNITS_PER_NEWTON;
          }

          if (initialized && typeof cb === 'function') cb(getCoefficients(), this);
        },

        getCoefficients = function() {
          return {
            epsilon: epsilon,
            sigma  : sigma,
            rmin   : rmin
          };
        },

        validateEpsilon = function(e) {
          if (e == null || parseFloat(e) !== e) {
            throw new Error("lennardJones: epsilon value " + e + " is invalid");
          }
        },

        validateSigma = function(s) {
          if (s == null || parseFloat(s) !== s || s <= 0) {
            throw new Error("lennardJones: sigma value " + s + " is invalid");
          }
        },

        // this object
        calculator;

        // At creation time, there must be a valid epsilon and sigma ... we're not gonna check during
        // inner-loop force calculations!
        validateEpsilon(params.epsilon);
        validateSigma(params.sigma);

        // Initialize coefficients to passed-in values, skipping setCoefficients callback
        setCoefficients(params.epsilon, params.sigma);
        initialized = true;

    return calculator = {

      getCoefficients: getCoefficients,

      setEpsilon: function(e) {
        validateEpsilon(e);
        setCoefficients(e, sigma);
      },

      setSigma: function(s) {
        validateSigma(s);
        setCoefficients(epsilon, s);
      },

      /**
        Input units: r_sq: nm^2
        Output units: eV

        minimum is at r=rmin, V(rmin) = 0
      */
      potentialFromSquaredDistance: function(r_sq) {
        if (!r_sq) return -Infinity;
        return alpha_Potential*Math.pow(r_sq, -6) - beta_Potential*Math.pow(r_sq, -3);
      },

      /**
        Input units: r: nm
        Output units: eV
      */
      potential: function(r) {
        return calculator.potentialFromSquaredDistance(r*r);
      },

      /**
        Input units: r_sq: nm^2
        Output units: MW Force Units / nm (= Dalton / fs^2)
      */
      forceOverDistanceFromSquaredDistance: function(r_sq) {
        // optimizing divisions actually does appear to be *slightly* faster
        var r_minus2nd  = 1 / r_sq,
            r_minus6th  = r_minus2nd * r_minus2nd * r_minus2nd,
            r_minus8th  = r_minus6th * r_minus2nd,
            r_minus14th = r_minus8th * r_minus6th;

        return alpha_Force*r_minus14th - beta_Force*r_minus8th;
      },

      /**
        Input units: r: nm
        Output units: MW Force Units (= Dalton * nm / fs^2)
      */
      force: function(r) {
        return r * calculator.forceOverDistanceFromSquaredDistance(r*r);
      }
    };
  };
});

/*global define: true */

// Module can be used both in Node.js environment and in Web browser
// using RequireJS. RequireJS Optimizer will strip out this if statement.


define('models/md2d/models/engine/potentials/index',['require','exports','module','./coulomb','./lennard-jones'],function (require, exports, module) {
  exports.coulomb = require('./coulomb');
  exports.lennardJones = require('./lennard-jones');
});



/*
Custom pairwise Lennard Jones properties.
*/


(function() {
  var __hasProp = {}.hasOwnProperty;

  define('cs!models/md2d/models/engine/pairwise-lj-properties',['require','models/md2d/models/metadata','common/validator'],function(require) {
    var PairwiseLJProperties, metadata, validator;
    metadata = require('models/md2d/models/metadata');
    validator = require("common/validator");
    return PairwiseLJProperties = (function() {

      function PairwiseLJProperties(engine) {
        this._engine = engine;
        this._data = {};
      }

      PairwiseLJProperties.prototype.registerChangeHooks = function(changePreHook, changePostHook) {
        this._changePreHook = changePreHook;
        return this._changePostHook = changePostHook;
      };

      PairwiseLJProperties.prototype.set = function(i, j, props) {
        var key;
        props = validator.validate(metadata.pairwiseLJProperties, props);
        this._changePreHook();
        if (!(this._data[i] != null)) {
          this._data[i] = {};
        }
        if (!(this._data[j] != null)) {
          this._data[j] = {};
        }
        if (!(this._data[i][j] != null)) {
          this._data[i][j] = this._data[j][i] = {};
        }
        for (key in props) {
          if (!__hasProp.call(props, key)) continue;
          this._data[i][j][key] = props[key];
        }
        this._engine.setPairwiseLJProperties(i, j);
        return this._changePostHook();
      };

      PairwiseLJProperties.prototype.remove = function(i, j) {
        this._changePreHook();
        delete this._data[i][j];
        delete this._data[j][i];
        this._engine.setPairwiseLJProperties(i, j);
        return this._changePostHook();
      };

      PairwiseLJProperties.prototype.get = function(i, j) {
        if (this._data[i] && this._data[i][j]) {
          return this._data[i][j];
        } else {
          return void 0;
        }
      };

      PairwiseLJProperties.prototype.deserialize = function(array) {
        var el1, el2, props, _i, _len;
        for (_i = 0, _len = array.length; _i < _len; _i++) {
          props = array[_i];
          props = validator.validateCompleteness(metadata.pairwiseLJProperties, props);
          el1 = props.element1;
          el2 = props.element2;
          delete props.element1;
          delete props.element2;
          this.set(el1, el2, props);
        }
      };

      PairwiseLJProperties.prototype.serialize = function() {
        var innerObj, key1, key2, props, result, _ref;
        result = [];
        _ref = this._data;
        for (key1 in _ref) {
          if (!__hasProp.call(_ref, key1)) continue;
          innerObj = _ref[key1];
          for (key2 in innerObj) {
            if (!__hasProp.call(innerObj, key2)) continue;
            if (key1 < key2) {
              props = this.get(key1, key2);
              props.element1 = Number(key1);
              props.element2 = Number(key2);
              result.push(props);
            }
          }
        }
        return result;
      };

      /*
          Clone-Restore Interface.
      */


      PairwiseLJProperties.prototype.clone = function() {
        return $.extend(true, {}, this._data);
      };

      PairwiseLJProperties.prototype.restore = function(state) {
        var innerObj, key1, key2, _ref;
        this._data = state;
        _ref = this._data;
        for (key1 in _ref) {
          if (!__hasProp.call(_ref, key1)) continue;
          innerObj = _ref[key1];
          for (key2 in innerObj) {
            if (!__hasProp.call(innerObj, key2)) continue;
            if (key1 < key2) {
              this._engine.setPairwiseLJProperties(key1, key2);
            }
          }
        }
      };

      return PairwiseLJProperties;

    })();
  });

}).call(this);

/*global define */

// Cell lists (also sometimes referred to as Cell linked-lists) are a tool for
// finding all atom pairs within a given cut-off distance of each other in
// Molecular dynamics simulations.
// See: http://en.wikipedia.org/wiki/Cell_lists

define('models/md2d/models/engine/cell-list',[],function () {

  return function CellList(width, height, cellSize) {
    var api,
        colsNum,
        rowsNum,
        cellsNum,
        cell,

        init = function () {
          var i;
          colsNum = Math.ceil(width / cellSize);
          rowsNum = Math.ceil(height / cellSize);
          cellsNum = colsNum * rowsNum;
          cell = new Array(cellsNum);
          for(i = 0; i < cellsNum; i++) {
            cell[i] = [];
          }
        };

    init ();

    // Public API.
    api = {
      reinitialize: function (newWidth, newHeight, newCellSize) {
        var change = false;
        if (newWidth !== undefined) {
          if (newWidth !== width) { width = newWidth; change = true; }
        }
        if (newHeight !== undefined) {
          if (newHeight !== height) { height = newHeight; change = true; }
        }
        if (newCellSize !== undefined) {
          if (newCellSize !== cellSize) { cellSize = newCellSize; change = true; }
        }
        if (change) init();
      },

      addToCell: function (atomIdx, x, y) {
        var cellIdx = Math.floor(y / cellSize) * colsNum + Math.floor(x / cellSize);
        cell[cellIdx].push(atomIdx);
      },

      getCell: function (idx) {
        return cell[idx];
      },

      getRowsNum: function () {
        return rowsNum;
      },

      getColsNum: function () {
        return colsNum;
      },

      getNeighboringCells: function (rowIdx, colIdx) {
        var cellIdx = rowIdx * colsNum + colIdx,
            result = [];

        // Upper right.
        if (colIdx + 1 < colsNum && rowIdx + 1 < rowsNum) result.push(cell[cellIdx + colsNum + 1]);
        // Right.
        if (colIdx + 1 < colsNum) result.push(cell[cellIdx + 1]);
        // Bottom right.
        if (colIdx + 1 < colsNum && rowIdx - 1 >= 0) result.push(cell[cellIdx - colsNum + 1]);
        // Bottom.
        if (rowIdx - 1 >= 0) result.push(cell[cellIdx - colsNum]);

        return result;
      },

      clear: function () {
        var i;
        for (i = 0; i < cellsNum; i++) {
          cell[i].length = 0;
        }
      }
    };

    return api;
  };

});

/*global define */

// A Verlet list (named after Loup Verlet) is a data structure in molecular dynamics simulations
// to efficiently maintain a list of all particles within a given cut-off distance of each other.
// See: http://en.wikipedia.org/wiki/Verlet_list

define('models/md2d/models/engine/neighbor-list',['require','arrays','common/array-types'],function (require) {
  // Dependencies.
  var arrays     = require('arrays'),
      arrayTypes = require('common/array-types');

  return function NeighborList(atomsNum, maxDisplacement) {
    var api,
        maxAtomsNum,
        listIdx,
        listCapacity,
        list,
        head,
        tail,
        x,
        y,
        forceUpdate,

        init = function () {
          // Keep maximum capacity of lists bigger than actual number of atoms.
          maxAtomsNum = atomsNum + 10;
          listIdx = 0;
          listCapacity = maxAtomsNum * (maxAtomsNum - 1) / 2;
          forceUpdate = true;

          list = arrays.create(listCapacity, 0, arrayTypes.int16Type);
          head = arrays.create(maxAtomsNum, -1, arrayTypes.int16Type);
          tail = arrays.create(maxAtomsNum, -1, arrayTypes.int16Type);
          // Fill x and y with Infinity, so shouldUpdate(..)
          // will return true during first call after initialization.
          x    = arrays.create(maxAtomsNum, Infinity, arrayTypes.floatType);
          y    = arrays.create(maxAtomsNum, Infinity, arrayTypes.floatType);
        };

    init();

    // Public API.
    api = {
      reinitialize: function (newAtomsNum, newMaxDisplacement) {
        atomsNum = newAtomsNum;
        maxDisplacement = newMaxDisplacement;
        forceUpdate = true;

        if (atomsNum > maxAtomsNum) {
          init();
        }
      },
      clear: function () {
        var i;
        listIdx = 0;
        for (i = 0; i < atomsNum; i++) {
          head[i] = tail[i] = -1;
        }
      },
      getList: function () {
        return list;
      },
      markNeighbors: function (i, j) {
        if (head[i] < 0) {
          head[i] = listIdx;
        }
        list[listIdx] = j;
        listIdx++;
        tail[i] = listIdx;
      },
      getStartIdxFor: function (i) {
        return head[i];
      },
      getEndIdxFor: function (i) {
        return tail[i];
      },
      saveAtomPosition: function (i, xCoord, yCoord) {
        x[i] = xCoord;
        y[i] = yCoord;
      },
      invalidate: function () {
        forceUpdate = true;
      },
      shouldUpdate: function (newX, newY) {
        var maxDx = -Infinity,
            maxDy = -Infinity,
            i;

        if (forceUpdate) {
          forceUpdate = false;
          return true;
        }

        for (i = 0; i < atomsNum; i++) {
          if (Math.abs(newX[i] - x[i]) > maxDx) {
            maxDx = Math.abs(newX[i] - x[i]);
          }
          if (Math.abs(newY[i] - y[i]) > maxDy) {
            maxDy = Math.abs(newY[i] - y[i]);
          }
        }

        return Math.sqrt(maxDx * maxDx + maxDy * maxDy) > maxDisplacement;
      }
    };

    return api;
  };

});

/*global define */

// The PluginController manages an array of plugins, and can call arbitrary functions
// on any registered plugin that responds to that function.

// TODO:
//
//  1. Plugins should define engine component and modeler component
//  2. Plugins should validate their own properties
//  3. Plugin controller should handle initialization of plugins and mixing of appropriate methods
//     and properties into the modeler layer. The main modeler and engine layers should not have to
//     know details about which plugins are available, what they're named, where they are located,
//     etc.
//  4. Plugin controller have a 'getPluginFunction' method which accepts a function name and returns
//     a function that, when called, does the same thing as callPluginFunction(<function name>,...).
//     This avoids having to look up the plugin function by name every time it is called.

define('common/models/plugin-controller',[],function () {

  return function PluginController() {
    var plugins = [];

    // Public API.
    return {
      registerPlugin: function(plugin) {
        plugins.push(plugin);
      },

      /**
        Calls method 'funcName' of every plugin, for those plugins which have a property 'funcName',
        in the context of the plugin (i.e., 'this' value is the plugin object) and with the elements
        of the array 'args' as the argument array of the invocation.

        If 'callback' is defined, it will be invoked with the callback.

        The callback signature is callback(returnValue, index, plugin) where returnValue is the
        return value of the method called, i is the index of the plugin, and plugin is the plugin
        object itself.
      */
      callPluginFunction: function(funcName, args, callback) {
        var i, ii, func, retVal;

        for (i = 0, ii = plugins.length; i<ii; i++) {
          func = plugins[i][funcName];
          if (func) {
            retVal = func.apply(plugins[i], args);
          }
          if (callback) {
            callback(retVal, i, plugins[i]);
          }
        }
      }
    };
  };

});

/*global define: true */

define('models/md2d/models/engine/md2d',['require','exports','module','arrays','common/array-types','common/console','./constants/index','cs!models/md2d/models/aminoacids-helper','./math/index','./potentials/index','./potentials/index','cs!./pairwise-lj-properties','common/models/engines/clone-restore-wrapper','./cell-list','./neighbor-list','common/models/plugin-controller','./utils'],function (require, exports) {

  var arrays               = require('arrays'),
      arrayTypes           = require('common/array-types'),
      console              = require('common/console'),
      constants            = require('./constants/index'),
      unit                 = constants.unit,
      aminoacidsHelper     = require('cs!models/md2d/models/aminoacids-helper'),
      math                 = require('./math/index'),
      coulomb              = require('./potentials/index').coulomb,
      lennardJones         = require('./potentials/index').lennardJones,
      PairwiseLJProperties = require('cs!./pairwise-lj-properties'),
      CloneRestoreWrapper  = require('common/models/engines/clone-restore-wrapper'),
      CellList             = require('./cell-list'),
      NeighborList         = require('./neighbor-list'),
      PluginController     = require('common/models/plugin-controller'),
      utils                = require('./utils'),

      BOLTZMANN_CONSTANT_IN_JOULES = constants.BOLTZMANN_CONSTANT.as( unit.JOULES_PER_KELVIN ),

      cross = function(a0, a1, b0, b1) {
        return a0*b1 - a1*b0;
      },

      sumSquare = function(a,b) {
        return a*a + b*b;
      },

      /**
        Convert total kinetic energy in the container of N atoms to a temperature in Kelvin.

        Input units:
          KE: "MW Energy Units" (Dalton * nm^2 / fs^2)
        Output units:
          T: K
      */
      convertKEtoT = function(totalKEinMWUnits, N) {
        if (N === 0) return 0;
        var averageKEinMWUnits = totalKEinMWUnits / N,
            averageKEinJoules = constants.convert(averageKEinMWUnits, { from: unit.MW_ENERGY_UNIT, to: unit.JOULE });
        return averageKEinJoules / BOLTZMANN_CONSTANT_IN_JOULES;
      },

      /**
        Convert a temperature in Kelvin to the total kinetic energy in the container of N atoms.

        Input units:
          T: K
        Output units:
          KE: "MW Energy Units" (Dalton * nm^2 / fs^2)
      */
      convertTtoKE = function(T, N) {
        var averageKEinJoules  = T * BOLTZMANN_CONSTANT_IN_JOULES,
            averageKEinMWUnits = constants.convert(averageKEinJoules, { from: unit.JOULE, to: unit.MW_ENERGY_UNIT }),
            totalKEinMWUnits = averageKEinMWUnits * N;

        return totalKEinMWUnits;
      },

      validateTemperature = function(t) {
        var temperature = parseFloat(t);

        if (isNaN(temperature)) {
          throw new Error("md2d: requested temperature " + t + " could not be understood.");
        }
        if (temperature < 0) {
          throw new Error("md2d: requested temperature " + temperature + " was less than zero");
        }
        if (temperature === Infinity) {
          throw new Error("md2d: requested temperature was Infinity!");
        }
      };

  exports.createEngine = function() {

    var // the object to be returned
        engine,

        // Whether system dimensions have been set. This is only allowed to happen once.
        sizeHasBeenInitialized = false,

        pluginController = new PluginController(),

        // Whether to simulate Coulomb forces between particles.
        useCoulombInteraction = false,

        // Dielectric constant, it influences Coulomb interaction.
        // E.g. a dielectric of 80 means a Coulomb force 1/80th as strong.
        dielectricConst = 1,

        // Whether dielectric effect should be realistic or simplified. Realistic
        // version takes into account distance between charged particles and reduces
        // dielectric constant when particles are closer to each other.
        realisticDielectricEffect = true,

        // Parameter that reflects the watery extent of the solvent, when an effective
        // hydrophobic/hydrophilic interaction is used. A negative value represents oil environment
        // (usually -1). A positive one represents water environment (usually 1). A zero value means vacuum.
        solventForceType = 0,

        // State describing whether DNA translation is in progress.
        // TODO: move all functionality connected with DNA and proteins to engine plugins!
        dnaTranslationInProgress = false,

        // Parameter that influences strength of force applied to amino acids by water of oil (solvent).
        solventForceFactor = 1,

        // Additional force applied to amino acids that depends on distance from the center of mass. It affects
        // only AAs which are pulled into the center of mass (to stabilize shape of the protein).
        additionalSolventForceMult = 25,
        additionalSolventForceThreshold = 3,

        // Whether to simulate Lennard Jones forces between particles.
        useLennardJonesInteraction = true,

        // Whether to use the thermostat to maintain the system temperature near T_target.
        useThermostat = false,

        // A value to use in calculating if two atoms are close enough for a VDW line to be displayed
        vdwLinesRatio = 1.67,

        // If a numeric value include gravitational field in force calculations,
        // otherwise value should be false
        gravitationalField = false,

        // Desired system temperature, in Kelvin.
        T_target,

        // System dimensions as [x, y] in nanometers. Default value can be changed until particles are created.
        size = [10, 10],

        // System dimensions as minX, minY, maxX, maxY. Default value can be changed until turles are created.
        minX =  0,
        minY =  0,
        maxX = 10,
        maxY = 10,

        // Viscosity of the medium of the model
        viscosity,

        // The current model time, in femtoseconds.
        time = 0,

        // The current integration time step, in femtoseconds.
        dt,

        // Square of integration time step, in fs^2.
        dt_sq,

        // ####################################################################
        //                      Atom Properties

        // Individual property arrays for the atoms, indexed by atom number
        radius, px, py, x, y, vx, vy, speed, ax, ay, charge, element, friction, radical, pinned, mass, hydrophobicity,
        // Helper array, which may be used by various engine routines traversing atoms in untypical order.
        // Make sure that you reset it before use. At the moment, it's used by updateAminoAcidForces() function.
        visited,

        // An object that contains references to the above atom-property arrays
        atoms,

        // The number of atoms in the system.
        N = 0,

        // ####################################################################
        //                      Element Properties

        // Individual property arrays for the elements
        elementMass,
        elementEpsilon,
        elementSigma,
        elementRadius,
        elementColor,

        // An object that contains references to the above element-property arrays
        elements,

        // Number of actual elements (may be smaller than the length of the property arrays).
        N_elements = 0,

        // Additional structure, keeping information if given element is represented by
        // some atom in the model. Necessary for effective max cut-off distance calculation.
        elementUsed = [],

        // ####################################################################
        //                      Custom Pairwise LJ Properties
        pairwiseLJProperties,

        // ####################################################################
        //                      Radial Bond Properties

        // Individual property arrays for the "radial" bonds, indexed by bond number
        radialBondAtom1Index,
        radialBondAtom2Index,
        radialBondLength,
        radialBondStrength,
        radialBondType,

        // An object that contains references to the above radial-bond-property arrays.
        // Left undefined if there are no radial bonds.
        radialBonds,

        // Number of actual radial bonds (may be smaller than the length of the property arrays).
        N_radialBonds = 0,

        // Flag indicating if some radial bonds were added or removed during the integration step.
        radialBondsChanged = false,

        // ####################################################################
        //                      Restraint Properties

        // Individual property arrays for the "restraint" bonds, indexed by bond number.
        restraintAtomIndex,
        restraintK,
        restraintX0,
        restraintY0,

        // An object that contains references to the above restraint-property arrays.
        // Left undefined if there are no restraints.
        restraints,

        // Number of actual restraint bonds (may be smaller than the length of the property arrays).
        N_restraints = 0,

        // ####################################################################
        //                      Angular Bond Properties

        // Individual property arrays for the "angular" bonds, indexed by bond number.
        angularBondAtom1Index,
        angularBondAtom2Index,
        angularBondAtom3Index,
        angularBondAngle,
        angularBondStrength,

        // An object that contains references to the above angular-bond-property arrays.
        // Left undefined if there are no angular bonds.
        angularBonds,

        // Number of actual angular bonds (may be smaller than the length of the property arrays).
        N_angularBonds = 0,

        // ####################################################################
        //                      Obstacle Properties

        // Individual properties for the obstacles
        obstacleX,
        obstacleY,
        obstacleWidth,
        obstacleHeight,
        obstacleVX,
        obstacleVY,
        obstacleExtAX,
        obstacleExtAY,
        obstacleFriction,
        obstacleMass,
        obstacleWestProbe,
        obstacleNorthProbe,
        obstacleEastProbe,
        obstacleSouthProbe,
        obstacleColor,
        obstacleVisible,

        // Properties used only during internal calculations (e.g. shouldn't
        // be returned during getObstacleProperties(i) call - TODO!).
        obstacleXPrev,
        obstacleYPrev,

        // ### Pressure calculation ###
        // Arrays containing sum of impulses 2mv/dt from atoms hitting the probe.
        // These values are later stored in pressureBuffers object, interpolated
        // (last average of last PRESSURE_BUFFERS_LEN values) and converted
        // to value in Bar by getPressureFromProbe() function.
        obstacleWProbeValue,
        obstacleNProbeValue,
        obstacleEProbeValue,
        obstacleSProbeValue,

        // An object that contains references to the above obstacle-property arrays.
        // Left undefined if there are no obstacles.
        obstacles,

        // Number of actual obstacles
        N_obstacles = 0,

        // ####################################################################
        //                      Shape Properties

        // Individual properties for the shapes
        shapeType,
        shapeX,
        shapeY,
        shapeWidth,
        shapeHeight,
        shapeFence,
        shapeColor,
        shapeLineColor,
        shapeLineDashes,
        shapeLineWeight,
        shapeLayer,
        shapeLayerPosition,
        shapeVisible,

        // An object that contains references to the above shape-property arrays.
        // Left undefined if there are no shapes.
        shapes,

        // Number of actual shapes
        N_shapes = 0,


        // ####################################################################
        //                      Line Properties

        // Individual properties for the lines
        lineX1,
        lineY1,
        lineX2,
        lineY2,
        lineBeginStyle,
        lineEndStyle,
        lineFence,
        lineLineColor,
        lineLineDashes,
        lineLineWeight,
        lineLayer,
        lineLayerPosition,
        lineVisible,

        // An object that contains references to the above line-property arrays.
        // Left undefined if there are no lines.
        lines,

        // Number of actual lines
        N_lines = 0,

        // ####################################################################
        //                      Electric Field Properties

        // Individual properties for the electric fields.
        electricFieldIntensity,
        electricFieldOrientation,
        electricFieldShapeIdx,

        // An object that contains references to the above shape-property arrays.
        // Left undefined if there are no electric fields.
        electricFields,

        // Number of actual electric fields.
        N_electricFields = 0,

        // ####################################################################
        //                      Misc Properties
        // Hash of arrays containing VdW pairs
        vdwPairs,

        // Number of VdW pairs
        N_vdwPairs,

        // Arrays of VdW pair atom #1 and atom #2 indices
        vdwPairAtom1Index,
        vdwPairAtom2Index,

        // Arrays for spring forces, which are forces defined between an atom and a point in space
        springForceAtomIndex,
        springForceX,
        springForceY,
        springForceStrength,

        // An array whose members are the above spring-force-property arrays
        springForces,

        // The number of spring forces currently being applied in the model.
        N_springForces = 0,

        // Cell list structure.
        cellList,

        // Neighbor (Verlet) list structure.
        neighborList,

        // Information whether neighbor list should be
        // recalculated in the current integration step.
        updateNeighborList,

        //
        // The location of the center of mass, in nanometers.
        x_CM, y_CM,

        // Linear momentum of the system, in Dalton * nm / fs.
        px_CM, py_CM,

        // Velocity of the center of mass, in nm / fs.
        vx_CM, vy_CM,

        // Angular momentum of the system wrt its center of mass
        L_CM,

        // (Instantaneous) moment of inertia of the system wrt its center of mass
        I_CM,

        // Angular velocity of the system about the center of mass, in radians / fs.
        // (= angular momentum about CM / instantaneous moment of inertia about CM)
        omega_CM,

        // instantaneous system temperature, in Kelvin
        T,

        // cutoff for force calculations, as a factor of sigma
        cutoff = 4,
        cutoffDistance_LJ_sq = [],

        // cutoff for neighbor list calculations, as a factor of sigma
        cutoffList = 4.1,
        cutoffNeighborListSquared = [],

        // Each object at ljCalculator[i,j] can calculate the magnitude of the Lennard-Jones force and
        // potential between elements i and j
        ljCalculator = [],

        // List of particles representing cysteine amino acid, which can possibly create disulphide bonds.
        // So, each cysteine in this list is NOT already connected to other cysteine.
        freeCysteinesList = [],

        // Initializes basic data structures.
        initialize = function () {
          createElementsArray(0);
          createAtomsArray(0);
          createAngularBondsArray(0);
          createRadialBondsArray(0);
          createRestraintsArray(0);
          createVdwPairsArray(0);
          createSpringForcesArray(0);
          createObstaclesArray(0);
          createShapesArray(0);
          createLinesArray(0);
          createElectricFieldsArray(0);

          // Custom pairwise properties.
          pairwiseLJProperties = new PairwiseLJProperties(engine);
        },

        // Throws an informative error if a developer tries to use the setCoefficients method of an
        // in-use LJ calculator. (Hint: for an interactive LJ chart, create a new LJ calculator with
        // the desired coefficients; call setElementProperties to change the LJ properties in use.)
        ljCoefficientChangeError = function() {
          throw new Error("md2d: Don't change the epsilon or sigma parameters of the LJ calculator being used by MD2D. Use the setElementProperties method instead.");
        },

        // Initialize epsilon, sigma, cutoffDistance_LJ_sq, cutoffNeighborListSquared, and ljCalculator
        // array elements for element pair i and j
        setPairwiseLJProperties = function(i, j) {
          var epsilon_i   = elementEpsilon[i],
              epsilon_j   = elementEpsilon[j],
              sigma_i     = elementSigma[i],
              sigma_j     = elementSigma[j],
              customProps = pairwiseLJProperties.get(i, j),
              e,
              s;

          if (customProps && customProps.epsilon !== undefined) {
            e = customProps.epsilon;
          } else {
            e = lennardJones.pairwiseEpsilon(epsilon_i, epsilon_j);
          }

          if (customProps && customProps.sigma !== undefined) {
            s = customProps.sigma;
          } else {
            s = lennardJones.pairwiseSigma(sigma_i, sigma_j);
          }

          // Cutoff for Lennard-Jones interactions.
          cutoffDistance_LJ_sq[i][j] = cutoffDistance_LJ_sq[j][i] = (cutoff * s) * (cutoff * s);
          // Cutoff for neighbor lists calculations.
          cutoffNeighborListSquared[i][j] = cutoffNeighborListSquared[j][i] = (cutoffList * s) * (cutoffList * s);

          ljCalculator[i][j] = ljCalculator[j][i] = lennardJones.newLJCalculator({
            epsilon: e,
            sigma:   s
          }, ljCoefficientChangeError);
        },

        // Calculates maximal cut-off used in the current model. Functions checks all used
        // elements at the moment. When new atom is added, maximum cut-off distance should
        // be recalculated.
        computeMaxCutoff = function() {
          var maxCutoff = 0,
              customProps,
              sigma,
              i, j;

          for (i = 0; i < N_elements; i++) {
            for (j = 0; j <= i; j++) {
              if (elementUsed[i] && elementUsed[j]) {
                customProps = pairwiseLJProperties.get(i, j);
                if (customProps && customProps.sigma !== undefined) {
                  sigma = customProps.sigma;
                } else {
                  sigma = lennardJones.pairwiseSigma(elementSigma[i], elementSigma[j]);
                }
                // Use cutoffList, as cell lists are used to calculate neighbor lists.
                if (cutoffList * sigma > maxCutoff) {
                  maxCutoff = cutoffList * sigma;
                }
              }
            }
          }
          // If maxCutoff === 0, return size of the model
          // as a default cutoff distance for empty model.
          return maxCutoff || Math.max(size[0], size[1]);
        },

        // Returns a minimal difference between "real" cutoff
        // and cutoff used in neighbor list. This can be considered
        // as a minimal displacement of atom, which triggers neighbor
        // list recalculation (or maximal allowed displacement to avoid
        // recalculation).
        computeNeighborListMaxDisplacement = function() {
          var maxDisplacement = Infinity,
              customProps,
              sigma,
              i, j;

          for (i = 0; i < N_elements; i++) {
            for (j = 0; j <= i; j++) {
              if (elementUsed[i] && elementUsed[j]) {
                customProps = pairwiseLJProperties.get(i, j);
                if (customProps && customProps.sigma !== undefined) {
                  sigma = customProps.sigma;
                } else {
                  sigma = lennardJones.pairwiseSigma(elementSigma[i], elementSigma[j]);
                }

                if ((cutoffList - cutoff) * sigma < maxDisplacement) {
                  maxDisplacement = (cutoffList - cutoff) * sigma;
                }
              }
            }
          }
          return maxDisplacement;
        },

        // Initializes special structure for short-range forces calculation
        // optimization. Cell lists support neighbor list.
        initializeCellList = function () {
          if (cellList === undefined) {
            cellList = new CellList(size[0], size[1], computeMaxCutoff());
          } else {
            cellList.reinitialize(size[0], size[1], computeMaxCutoff());
          }
        },

        // Initializes special structure for short-range forces calculation
        // optimization. Neighbor list cooperates with cell list.
        initializeNeighborList = function () {
          if (neighborList === undefined) {
            neighborList = new NeighborList(N, computeNeighborListMaxDisplacement());
          } else {
            neighborList.reinitialize(N, computeNeighborListMaxDisplacement());
          }
        },

        /**
          Set up "shortcut" references, e.g., x = atoms.x
        */
        assignShortcutReferences = {

          atoms: function() {
            radius         = atoms.radius;
            px             = atoms.px;
            py             = atoms.py;
            x              = atoms.x;
            y              = atoms.y;
            vx             = atoms.vx;
            vy             = atoms.vy;
            speed          = atoms.speed;
            ax             = atoms.ax;
            ay             = atoms.ay;
            charge         = atoms.charge;
            friction       = atoms.friction;
            radical        = atoms.radical;
            element        = atoms.element;
            pinned         = atoms.pinned;
            mass           = atoms.mass;
            hydrophobicity = atoms.hydrophobicity;
            visited        = atoms.visited;
          },

          radialBonds: function() {
            radialBondAtom1Index  = radialBonds.atom1;
            radialBondAtom2Index  = radialBonds.atom2;
            radialBondLength      = radialBonds.length;
            radialBondStrength    = radialBonds.strength;
            radialBondType        = radialBonds.type;
          },

          restraints: function() {
            restraintAtomIndex  = restraints.atomIndex;
            restraintK          = restraints.k;
            restraintX0         = restraints.x0;
            restraintY0         = restraints.y0;
          },

          angularBonds: function() {
            angularBondAtom1Index  = angularBonds.atom1;
            angularBondAtom2Index  = angularBonds.atom2;
            angularBondAtom3Index  = angularBonds.atom3;
            angularBondAngle       = angularBonds.angle;
            angularBondStrength    = angularBonds.strength;
          },

          elements: function() {
            elementMass    = elements.mass;
            elementEpsilon = elements.epsilon;
            elementSigma   = elements.sigma;
            elementRadius  = elements.radius;
            elementColor   = elements.color;
          },

          obstacles: function() {
            obstacleX           = obstacles.x;
            obstacleY           = obstacles.y;
            obstacleWidth       = obstacles.width;
            obstacleHeight      = obstacles.height;
            obstacleMass        = obstacles.mass;
            obstacleVX          = obstacles.vx;
            obstacleVY          = obstacles.vy;
            obstacleExtAX       = obstacles.externalAx;
            obstacleExtAY       = obstacles.externalAy;
            obstacleFriction    = obstacles.friction;
            obstacleWestProbe   = obstacles.westProbe;
            obstacleNorthProbe  = obstacles.northProbe;
            obstacleEastProbe   = obstacles.eastProbe;
            obstacleSouthProbe  = obstacles.southProbe;
            obstacleWProbeValue = obstacles.westProbeValue;
            obstacleNProbeValue = obstacles.northProbeValue;
            obstacleEProbeValue = obstacles.eastProbeValue;
            obstacleSProbeValue = obstacles.southProbeValue;
            obstacleXPrev       = obstacles.xPrev;
            obstacleYPrev       = obstacles.yPrev;
            obstacleColor       = obstacles.color;
            obstacleVisible     = obstacles.visible;
          },

          shapes: function() {
            shapeType          = shapes.type;
            shapeX             = shapes.x;
            shapeY             = shapes.y;
            shapeWidth         = shapes.width;
            shapeHeight        = shapes.height;
            shapeFence         = shapes.fence;
            shapeColor         = shapes.color;
            shapeLineColor     = shapes.lineColor;
            shapeLineDashes    = shapes.lineDashes;
            shapeLineWeight    = shapes.lineWeight;
            shapeLayer         = shapes.layer;
            shapeLayerPosition = shapes.layerPosition;
            shapeVisible       = shapes.visible;
          },

          lines: function() {
            lineX1            = lines.x1;
            lineY1            = lines.y1;
            lineX2            = lines.x2;
            lineY2            = lines.y2;
            lineBeginStyle    = lines.beginStyle;
            lineEndStyle      = lines.endStyle;
            lineFence         = lines.fence;
            lineLineColor     = lines.lineColor;
            lineLineDashes    = lines.lineDashes;
            lineLineWeight    = lines.lineWeight;
            lineLayer         = lines.layer;
            lineLayerPosition = lines.layerPosition;
            lineVisible       = lines.visible;
          },

          electricFields: function() {
            electricFieldIntensity    = electricFields.intensity;
            electricFieldOrientation  = electricFields.orientation;
            electricFieldShapeIdx = electricFields.shapeIdx;
          },

          springForces: function() {
            springForceAtomIndex = springForces[0];
            springForceX         = springForces[1];
            springForceY         = springForces[2];
            springForceStrength  = springForces[3];
          },

          vdwPairs: function () {
            vdwPairAtom1Index = vdwPairs.atom1;
            vdwPairAtom2Index = vdwPairs.atom2;
          }

        },

        createElementsArray = function(num) {
          elements = engine.elements = {};

          elements.mass    = arrays.create(num, 0, arrayTypes.floatType);
          elements.epsilon = arrays.create(num, 0, arrayTypes.floatType);
          elements.sigma   = arrays.create(num, 0, arrayTypes.floatType);
          elements.radius  = arrays.create(num, 0, arrayTypes.floatType);
          elements.color   = [];

          assignShortcutReferences.elements();
        },

        createAtomsArray = function(num) {
          atoms = {};

          // TODO. DRY this up by letting the property list say what type each array is
          atoms.radius         = arrays.create(num, 0, arrayTypes.floatType);
          atoms.px             = arrays.create(num, 0, arrayTypes.floatType);
          atoms.py             = arrays.create(num, 0, arrayTypes.floatType);
          atoms.x              = arrays.create(num, 0, arrayTypes.floatType);
          atoms.y              = arrays.create(num, 0, arrayTypes.floatType);
          atoms.vx             = arrays.create(num, 0, arrayTypes.floatType);
          atoms.vy             = arrays.create(num, 0, arrayTypes.floatType);
          atoms.speed          = arrays.create(num, 0, arrayTypes.floatType);
          atoms.ax             = arrays.create(num, 0, arrayTypes.floatType);
          atoms.ay             = arrays.create(num, 0, arrayTypes.floatType);
          atoms.charge         = arrays.create(num, 0, arrayTypes.floatType);
          atoms.friction       = arrays.create(num, 0, arrayTypes.floatType);
          atoms.radical        = arrays.create(num, 0, arrayTypes.uint8Type);
          atoms.element        = arrays.create(num, 0, arrayTypes.uint8Type);
          atoms.pinned         = arrays.create(num, 0, arrayTypes.uint8Type);
          atoms.mass           = arrays.create(num, 0, arrayTypes.floatType);
          atoms.hydrophobicity = arrays.create(num, 0, arrayTypes.int8Type);
          atoms.visited        = arrays.create(num, 0, arrayTypes.uint8Type);
          // For the sake of clarity, manage all atoms properties in one
          // place (engine). In the future, think about separation of engine
          // properties and view-oriented properties like these:
          atoms.marked         = arrays.create(num, 0, arrayTypes.uint8Type);
          atoms.visible        = arrays.create(num, 0, arrayTypes.floatType);
          atoms.draggable      = arrays.create(num, 0, arrayTypes.uint8Type);
          atoms.draggableWhenStopped = arrays.create(num, 1, arrayTypes.uint8Type);

          assignShortcutReferences.atoms();
        },

        createRadialBondsArray = function(num) {
          radialBonds = engine.radialBonds = {};

          radialBonds.atom1    = arrays.create(num, 0, arrayTypes.uint16Type);
          radialBonds.atom2    = arrays.create(num, 0, arrayTypes.uint16Type);
          radialBonds.length   = arrays.create(num, 0, arrayTypes.floatType);
          radialBonds.strength = arrays.create(num, 0, arrayTypes.floatType);
          radialBonds.type     = arrays.create(num, 0, arrayTypes.uint8Type);

          assignShortcutReferences.radialBonds();
        },

        createRestraintsArray = function(num) {
          restraints = engine.restraints = {};

          restraints.atomIndex = arrays.create(num, 0, arrayTypes.uint16Type);
          restraints.k         = arrays.create(num, 0, arrayTypes.floatType);
          restraints.x0        = arrays.create(num, 0, arrayTypes.floatType);
          restraints.y0        = arrays.create(num, 0, arrayTypes.floatType);

          assignShortcutReferences.restraints();
        },

        createAngularBondsArray = function(num) {
          angularBonds = engine.angularBonds = {};

          angularBonds.atom1    = arrays.create(num, 0, arrayTypes.uint16Type);
          angularBonds.atom2    = arrays.create(num, 0, arrayTypes.uint16Type);
          angularBonds.atom3    = arrays.create(num, 0, arrayTypes.uint16Type);
          angularBonds.angle    = arrays.create(num, 0, arrayTypes.floatType);
          angularBonds.strength = arrays.create(num, 0, arrayTypes.floatType);

          assignShortcutReferences.angularBonds();
        },

        createVdwPairsArray = function(num) {
          vdwPairs = engine.vdwPairs = {};

          vdwPairs.count = 0;
          vdwPairs.atom1 = arrays.create(num, 0, arrayTypes.uint16Type);
          vdwPairs.atom2 = arrays.create(num, 0, arrayTypes.uint16Type);
        },

        createSpringForcesArray = function(num) {
          springForces = engine.springForces = [];

          // TODO: not very descriptive. Use hash of arrays like elsewhere.
          springForces[0] = arrays.create(num, 0, arrayTypes.uint16Type);
          springForces[1] = arrays.create(num, 0, arrayTypes.floatType);
          springForces[2] = arrays.create(num, 0, arrayTypes.floatType);
          springForces[3] = arrays.create(num, 0, arrayTypes.floatType);

          assignShortcutReferences.springForces();
        },

        createObstaclesArray = function(num) {
          obstacles = engine.obstacles = {};

          obstacles.x               = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.y               = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.width           = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.height          = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.mass            = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.vx              = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.vy              = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.externalAx      = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.externalAy      = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.friction        = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.westProbe       = arrays.create(num, 0, arrayTypes.uint8Type);
          obstacles.northProbe      = arrays.create(num, 0, arrayTypes.uint8Type);
          obstacles.eastProbe       = arrays.create(num, 0, arrayTypes.uint8Type);
          obstacles.southProbe      = arrays.create(num, 0, arrayTypes.uint8Type);
          obstacles.westProbeValue  = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.northProbeValue = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.eastProbeValue  = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.southProbeValue = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.xPrev           = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.yPrev           = arrays.create(num, 0, arrayTypes.floatType);
          obstacles.color           = [];
          obstacles.visible         = arrays.create(num, 0, arrayTypes.uint8Type);
          obstacles.displayExternalAcceleration = arrays.create(num, 0, arrayTypes.uint8Type);

          assignShortcutReferences.obstacles();
        },

        createShapesArray = function(num) {
          shapes = engine.shapes = {};

          shapes.type          = [];
          shapes.x             = arrays.create(num, 0, arrayTypes.floatType);
          shapes.y             = arrays.create(num, 0, arrayTypes.floatType);
          shapes.width         = arrays.create(num, 0, arrayTypes.floatType);
          shapes.height        = arrays.create(num, 0, arrayTypes.floatType);
          shapes.color         = [];
          shapes.lineColor     = [];
          shapes.lineDashes    = [];
          shapes.lineWeight    = arrays.create(num, 0, arrayTypes.floatType);
          shapes.layer         = arrays.create(num, 0, arrayTypes.uint8Type);
          shapes.layerPosition = arrays.create(num, 0, arrayTypes.uint8Type);
          shapes.visible       = arrays.create(num, 0, arrayTypes.uint8Type);
          shapes.fence         = arrays.create(num, 0, arrayTypes.uint8Type);

          assignShortcutReferences.shapes();
        },

        createLinesArray = function(num) {
          lines = engine.lines = {};

          lines.x1            = arrays.create(num, 0, arrayTypes.floatType);
          lines.y1            = arrays.create(num, 0, arrayTypes.floatType);
          lines.x2            = arrays.create(num, 0, arrayTypes.floatType);
          lines.y2            = arrays.create(num, 0, arrayTypes.floatType);
          lines.beginStyle    = [];
          lines.endStyle      = [];
          lines.lineColor     = [];
          lines.lineDashes    = [];
          lines.lineWeight    = arrays.create(num, 0, arrayTypes.floatType);
          lines.layer         = arrays.create(num, 0, arrayTypes.uint8Type);
          lines.layerPosition = arrays.create(num, 0, arrayTypes.uint8Type);
          lines.visible       = arrays.create(num, 0, arrayTypes.uint8Type);
          lines.fence         = arrays.create(num, 0, arrayTypes.uint8Type);

          assignShortcutReferences.lines();
        },

        createElectricFieldsArray = function(num) {
          electricFields = engine.electricFields = {};

          electricFields.intensity   = arrays.create(num, 0, arrayTypes.floatType);
          electricFields.orientation = [];
          electricFields.shapeIdx    = [];

          assignShortcutReferences.electricFields();
        },

        // Calculates & returns instantaneous temperature of the system.
        computeTemperature = function() {
          var twoKE = 0,
              i;

          // Particles.
          for (i = 0; i < N; i++) {
            twoKE += mass[i] * (vx[i] * vx[i] + vy[i] * vy[i]);
          }
          // Obstacles.
          for (i = 0; i < N_obstacles; i++) {
            if (obstacleMass[i] !== Infinity) {
              twoKE += obstacleMass[i] *
                  (obstacleVX[i] * obstacleVX[i] + obstacleVY[i] * obstacleVY[i]);
            }
          }

          return convertKEtoT(twoKE / 2, N);
        },

        // Calculates & returns the instaneous temperature of a particular group of atoms
        computeTemperatureOfAtoms = function(atomIndices) {
          var twoKE = 0,
              i,
              j;

          // Particles.
          for (i = 0; i < atomIndices.length; i++) {
            j = atomIndices[i];
            twoKE += mass[j] * (vx[j] * vx[j] + vy[j] * vy[j]);
          }

          return convertKEtoT(twoKE / 2, atomIndices.length);
        },

        // Adds the velocity vector (vx_t, vy_t) to the velocity vector of particle i
        addVelocity = function(i, vx_t, vy_t) {
          vx[i] += vx_t;
          vy[i] += vy_t;

          px[i] = vx[i]*mass[i];
          py[i] = vy[i]*mass[i];
        },

        // Adds effect of angular velocity omega, relative to (x_CM, y_CM), to the velocity vector of particle i
        addAngularVelocity = function(i, omega) {
          vx[i] -= omega * (y[i] - y_CM);
          vy[i] += omega * (x[i] - x_CM);

          px[i] = vx[i]*mass[i];
          py[i] = vy[i]*mass[i];
        },

        // Subtracts the center-of-mass linear velocity and the system angular velocity from the velocity vectors
        removeTranslationAndRotationFromVelocities = function() {
          for (var i = 0; i < N; i++) {
            addVelocity(i, -vx_CM, -vy_CM);
            addAngularVelocity(i, -omega_CM);
          }
        },

        // currently unused, implementation saved here for future reference:

        // // Adds the center-of-mass linear velocity and the system angular velocity back into the velocity vectors
        // addTranslationAndRotationToVelocities = function() {
        //   for (var i = 0; i < N; i++) {
        //     addVelocity(i, vx_CM, vy_CM);
        //     addAngularVelocity(i, omega_CM);
        //   }
        // },

        // Subroutine that calculates the position and velocity of the center of mass, leaving these in x_CM, y_CM,
        // vx_CM, and vy_CM, and that then computes the system angular velocity around the center of mass, leaving it
        // in omega_CM.
        computeSystemTranslation = function() {
          var x_sum = 0,
              y_sum = 0,
              px_sum = 0,
              py_sum = 0,
              totalMass = engine.getTotalMass(),
              i;

          for (i = 0; i < N; i++) {
            x_sum += x[i];
            y_sum += y[i];
            px_sum += px[i];
            py_sum += py[i];
          }

          x_CM = x_sum / N;
          y_CM = y_sum / N;
          px_CM = px_sum;
          py_CM = py_sum;
          vx_CM = px_sum / totalMass;
          vy_CM = py_sum / totalMass;
        },

        // Subroutine that calculates the angular momentum and moment of inertia around the center of mass, and then
        // uses these to calculate the weighted angular velocity around the center of mass.
        // Updates I_CM, L_CM, and omega_CM.
        // Requires x_CM, y_CM, vx_CM, vy_CM to have been calculated.
        computeSystemRotation = function() {
          var L = 0,
              I = 0,
              m,
              i;

          for (i = 0; i < N; i++) {
            m = mass[i];
            // L_CM = sum over N of of mr_i x p_i (where r_i and p_i are position & momentum vectors relative to the CM)
            L += m * cross( x[i]-x_CM, y[i]-y_CM, vx[i]-vx_CM, vy[i]-vy_CM);
            I += m * sumSquare( x[i]-x_CM, y[i]-y_CM );
          }

          L_CM = L;
          I_CM = I;
          omega_CM = L_CM / I_CM;
        },

        computeCMMotion = function() {
          computeSystemTranslation();
          computeSystemRotation();
        },

        // ####################################################################
        // #              Functions handling different collisions.            #
        // ####################################################################

        // Constrain obstacle i to the area between the walls by simulating perfectly elastic collisions with the walls.
        bounceObstacleOffWalls = function(i) {
          var leftwall   = 0,
              bottomwall = 0,
              width  = size[0],
              height = size[1],
              rightwall = width - obstacleWidth[i],
              topwall   = height - obstacleHeight[i];

          // Bounce off vertical walls.
          if (obstacleX[i] < leftwall) {
            while (obstacleX[i] < leftwall - width) {
              obstacleX[i] += width;
            }
            obstacleX[i]  = leftwall + (leftwall - obstacleX[i]);
            obstacleVX[i] *= -1;
          } else if (obstacleX[i] > rightwall) {
            while (obstacleX[i] > rightwall + width) {
              obstacleX[i] -= width;
            }
            obstacleX[i]  = rightwall - (obstacleX[i] - rightwall);
            obstacleVX[i] *= -1;
          }

          // Bounce off horizontal walls.
          if (obstacleY[i] < bottomwall) {
            while (obstacleY[i] < bottomwall - height) {
              obstacleY[i] += height;
            }
            obstacleY[i]  = bottomwall + (bottomwall - obstacleY[i]);
            obstacleVY[i] *= -1;
          } else if (obstacleY[i] > topwall) {
            while (obstacleY[i] > topwall + width) {
              obstacleY[i] -= width;
            }
            obstacleY[i]  = topwall - (obstacleY[i] - topwall);
            obstacleVY[i] *= -1;
          }
        },

        // Constrain particle i to the area between the walls by simulating perfectly elastic collisions with the walls.
        // Note this may change the linear and angular momentum.
        bounceParticleOffWalls = function(i) {
          var r = radius[i],
              leftwall = r,
              bottomwall = r,
              width = size[0],
              height = size[1],
              rightwall = width - r,
              topwall = height - r;

          // Bounce off vertical walls.
          if (x[i] < leftwall) {
            while (x[i] < leftwall - width) {
              x[i] += width;
            }
            x[i]  = leftwall + (leftwall - x[i]);
            vx[i] *= -1;
            px[i] *= -1;
          } else if (x[i] > rightwall) {
            while (x[i] > rightwall + width) {
              x[i] -= width;
            }
            x[i]  = rightwall - (x[i] - rightwall);
            vx[i] *= -1;
            px[i] *= -1;
          }

          // Bounce off horizontal walls
          if (y[i] < bottomwall) {
            while (y[i] < bottomwall - height) {
              y[i] += height;
            }
            y[i]  = bottomwall + (bottomwall - y[i]);
            vy[i] *= -1;
            py[i] *= -1;
          } else if (y[i] > topwall) {
            while (y[i] > topwall + height) {
              y[i] -= height;
            }
            y[i]  = topwall - (y[i] - topwall);
            vy[i] *= -1;
            py[i] *= -1;
          }
        },
        bounceParticleOffObstacles = function(i, x_prev, y_prev, updatePressure) {
          // fast path if no obstacles
          if (N_obstacles < 1) return;

          var r,
              xi,
              yi,

              j,

              x_left,
              x_right,
              y_top,
              y_bottom,
              x_left_prev,
              x_right_prev,
              y_top_prev,
              y_bottom_prev,
              vxPrev,
              vyPrev,
              obs_vxPrev,
              obs_vyPrev,
              atom_mass,
              obs_mass,
              totalMass,
              bounceDirection;

          r = radius[i];
          xi = x[i];
          yi = y[i];

          for (j = 0; j < N_obstacles; j++) {

            x_left = obstacleX[j] - r;
            x_right = obstacleX[j] + obstacleWidth[j] + r;
            y_top = obstacleY[j] + obstacleHeight[j] + r;
            y_bottom = obstacleY[j] - r;

            x_left_prev = obstacleXPrev[j] - r;
            x_right_prev = obstacleXPrev[j] + obstacleWidth[j] + r;
            y_top_prev = obstacleYPrev[j] + obstacleHeight[j] + r;
            y_bottom_prev = obstacleYPrev[j] - r;

            // Reset bounceDirection, which indicates collision type.
            bounceDirection = 0;
            // Check all possibilities for a collision with the rectangular obstacle.
            if (xi > x_left && xi < x_right && yi > y_bottom && yi < y_top) {
              if (x_prev <= x_left_prev) {
                x[i] = x_left - (xi - x_left);
                bounceDirection = 1; // West wall collision.
              } else if (x_prev >= x_right_prev) {
                x[i] = x_right + (x_right - xi);
                bounceDirection = 2; // East wall collision.
              } else if (y_prev <= y_bottom_prev) {
                y[i] = y_bottom - (yi - y_bottom);
                bounceDirection = -1; // South wall collision.
              } else if (y_prev >= y_top_prev) {
                y[i] = y_top  + (y_top - yi);
                bounceDirection = -2; // North wall collision.
              }
            }

            obs_mass = obstacleMass[j];

            if (bounceDirection !== 0) {
              if (obs_mass !== Infinity) {
                // if we have real mass, perform a perfectly-elastic collision
                atom_mass = mass[i];
                totalMass = obs_mass + atom_mass;
                if (bounceDirection > 0) {
                  vxPrev = vx[i];
                  obs_vxPrev = obstacleVX[j];

                  vx[i] = (vxPrev * (atom_mass - obs_mass) + (2 * obs_mass * obs_vxPrev)) / totalMass;
                  obstacleVX[j] = (obs_vxPrev * (obs_mass - atom_mass) + (2 * px[i])) / totalMass;
                } else {
                  vyPrev = vy[i];
                  obs_vyPrev = obstacleVY[j];

                  vy[i] = (vyPrev * (atom_mass - obs_mass) + (2 * obs_mass * obs_vyPrev)) / totalMass;
                  obstacleVY[j] = (obs_vyPrev * (obs_mass - atom_mass) + (2 * py[i])) / totalMass;
                }
              } else {
                // if we have infinite mass, just reflect (like a wall)
                if (bounceDirection > 0) {
                  vx[i] *= -1;
                } else {
                  vy[i] *= -1;
                }
              }

              if (updatePressure) {
                // Update pressure probes if there are any.
                if (obstacleWestProbe[j] && bounceDirection === 1) {
                  // 1 is west wall collision.
                  obstacleWProbeValue[j] += mass[i] * ((vxPrev ? vxPrev : -vx[i]) - vx[i]);
                } else if (obstacleEastProbe[j] && bounceDirection === 2) {
                  // 2 is west east collision.
                  obstacleEProbeValue[j] += mass[i] * (vx[i] - (vxPrev ? vxPrev : -vx[i]));
                } else if (obstacleSouthProbe[j] && bounceDirection === -1) {
                  // -1 is south wall collision.
                  obstacleSProbeValue[j] += mass[i] * ((vyPrev ? vyPrev : -vy[i]) - vy[i]);
                } else if (obstacleNorthProbe[j] && bounceDirection === -2) {
                  // -2 is north wall collision.
                  obstacleNProbeValue[j] += mass[i] * (vy[i] - (vyPrev ? vyPrev : -vy[i]));
                }
              }

            }
          }
        },

        bounceParticleOffShapes = function(i, x_prev, y_prev) {
          // fast path if no shapes
          if (N_shapes < 1) return;

          var r,
              xi,
              yi,

              j,

              x_inside_left,
              x_inside_right,
              y_inside_top,
              y_inside_bottom,
              x_outside_left,
              x_outside_right,
              y_outside_top,
              y_outside_bottom,

              shapeX_offset,shapeY_offset,

              tx,ty,
              tx_prev,ty_prev,
              ar,br,

              a,b,c,
              f1x,f1y,f1d,
              f2x,f2y,f2d,
              tvx,tvy,
              mx,my,
              nx,ny,nd;

          r = radius[i];
          xi = x[i];
          yi = y[i];


          for ( j = 0; j < N_shapes; j++) {

            if (!shapeFence[j])
              continue;

            if (shapeType[j] === 'rectangle') {

              x_outside_left = shapeX[j] - r;
              x_outside_right = shapeX[j] + shapeWidth[j] + r;
              y_outside_top = shapeY[j] + shapeHeight[j] + r;
              y_outside_bottom = shapeY[j] - r;

              x_inside_left = shapeX[j] + r;
              x_inside_right = shapeX[j] + shapeWidth[j] - r;
              y_inside_top = shapeY[j] + shapeHeight[j] - r;
              y_inside_bottom = shapeY[j] + r;

              // Check all outside collisions
              if (xi > x_outside_left && xi < x_outside_right && yi > y_outside_bottom && yi < y_outside_top) {
                if (x_prev <= x_outside_left) {
                  x[i] = x_outside_left - (xi - x_outside_left);
                  vx[i] *= -1;
                }
                else if (x_prev >= x_outside_right) {
                  x[i] = x_outside_right + (x_outside_right - xi);
                  vx[i] *= -1;
                }
                else if (y_prev <= y_outside_bottom) {
                  y[i] = y_outside_bottom - (yi - y_outside_bottom);
                  vy[i] *= -1;
                }
                else if (y_prev >= y_outside_top) {
                  y[i] = y_outside_top + (y_outside_top - yi);
                  vy[i] *= -1;
                }
              }
              //Check all inside collisions
              if (x_prev > x_inside_left && x_prev < x_inside_right && y_prev > y_inside_bottom && y_prev < y_inside_top) {
                if (xi <= x_inside_left) {
                  x[i] = x_inside_left + (x_inside_left - xi);
                  vx[i] *= -1;
                } else if (xi >= x_inside_right) {
                  x[i] = x_inside_right - (xi - x_inside_right);
                  vx[i] *= -1;
                }
                if (yi <= y_inside_bottom) {
                  y[i] = y_inside_bottom + (y_inside_bottom - yi);
                  vy[i] *= -1;
                } else if (yi >= y_inside_top) {
                  y[i] = y_inside_top - (yi - y_inside_top);
                  vy[i] *= -1;
                }
              }
            }
            else if (shapeType[j] === 'ellipse') {
              a = shapeWidth[j] / 2;
              b = shapeHeight[j] / 2;
              // Transform points from model space to ellipse space
              // to facilitate collision checking
              shapeX_offset = shapeX[j] + a;
              shapeY_offset = shapeY[j] + b;
              tx = (xi - shapeX_offset) / (a + r);
              ty = (yi - shapeY_offset) / (b + r);
              tx *= tx;
              ty *= ty;
              tx_prev = (x_prev - shapeX_offset) / (a + r);
              ty_prev = (y_prev - shapeY_offset) / (b + r);
              tx_prev *= tx_prev;
              ty_prev *= ty_prev;
              ar = (a + r) / (a - r);
              br = (b + r) / (b - r);
              ar *= ar;
              br *= br;
              if (tx + ty <= 1 && tx_prev + ty_prev > 1 || tx * ar + ty * br >= 1 && tx_prev * ar + ty_prev * br < 1) {

                // Calculate the two foci
                if (shapeWidth[j] > shapeHeight[j]) {
                  c = Math.sqrt(a * a - b * b);
                  f1x = shapeX[j] + a + c;
                  f2x = shapeX[j] + a - c;
                  f1y = f2y = shapeY[j] + b;
                } else {
                  c = Math.sqrt(b * b - a * a);
                  f1x = f2x = shapeX[j] + a;
                  f1y = shapeY[j] + b + c;
                  f2y = shapeY[j] + b - c;
                }


                // Determine the midpoint of the atom's motion path
                // so it can be used as an approximate point of collision
                mx = (xi + x_prev) / 2;
                my = (yi + y_prev) / 2;

                // Determine the distance from the point of collision
                // to both foci
                f1d = Math.sqrt(sumSquare(f1x - mx, f1y - my));
                f2d = Math.sqrt(sumSquare(f2x - mx, f2x - mx));

                // Calculate the angle bisector which is the normal vector
                nx = mx - (f1x * f2d + f2x * f1d) / (f1d + f2d);
                ny = my - (f1y * f2d + f2y * f1d) / (f1d + f2d);

                // Normalize the normal vector
                nd = Math.sqrt(sumSquare(nx, ny));
                nx /= nd;
                ny /= nd;

                // Reflect the atom's position off the normal vector
                x[i] = (mx - x_prev) - 2 * ((mx - x_prev) * nx + (my - y_prev) * ny) * nx + mx;
                y[i] = (my - y_prev) - 2 * ((mx - x_prev) * nx + (my - y_prev) * ny) * ny + my;

                // Reflect the atom's velocity off the normal vector
                tvx = vx[i];
                tvy = vy[i];
                vx[i] = (tvx - 2 * (tvx * nx + tvy * ny) * nx);
                vy[i] = (tvy - 2 * (tvx * nx + tvy * ny) * ny);
              }
            }
          }
        },

        bounceParticleOffLines = function(i, x_prev, y_prev) {
          // fast path if no lines
          if (N_lines < 1) return;

          var r,
              ld,
              atom1_to_line,
              atom2_to_line,
              line1_to_atom,
              line2_to_atom,
              mx,my,
              nx,ny,nd,
              tvx,tvy,
              j,
              xi, yi;

          r = radius[i];
          xi = x[i];
          yi = y[i];

          for (j = 0; j < N_lines; j++) {
            if (!lineFence[j])
              continue;

            // Find a bunch of cross products to check collision
            line1_to_atom = cross(x_prev - lineX1[j], y_prev - lineY1[j], xi - lineX1[j], yi - lineY1[j]);
            line2_to_atom = cross(x_prev - lineX2[j], y_prev - lineY2[j], xi - lineX2[j], yi - lineY2[j]);
            if (line1_to_atom * line2_to_atom < 0) {
              ld = Math.sqrt(sumSquare(lineX1[j] - lineX2[j], lineY1[j] - lineY2[j]));
              atom1_to_line = cross(lineX2[j] - x_prev, lineY2[j] - y_prev, lineX1[j] - x_prev, lineY1[j] - y_prev);
              atom2_to_line = cross(lineX2[j] - xi, lineY2[j] - yi, lineX1[j] - xi, lineY1[j] - yi);
              if ((atom1_to_line < 0 && atom2_to_line > -r*ld || atom1_to_line > 0 && atom2_to_line < r*ld) &&
                   atom1_to_line * line1_to_atom > 0) {
                // Collision!

                // Determine the midpoint of the atom's motion path
                // so it can be used as an approximate point of collision
                mx = (xi + x_prev) / 2;
                my = (yi + y_prev) / 2;

                // Caclulate the normal vector (just perpendicular to the line)
                nx = lineY2[j] - lineY1[j];
                ny = -(lineX2[j] - lineX1[j]);

                // Normalize the normal vector
                nd = Math.sqrt(sumSquare(nx, ny));
                nx /= nd;
                ny /= nd;

                // Reflect the atom's position off the normal vector
                x[i] = (mx - x_prev) - 2 * ((mx - x_prev) * nx + (my - y_prev) * ny) * nx + mx;
                y[i] = (my - y_prev) - 2 * ((mx - x_prev) * nx + (my - y_prev) * ny) * ny + my;

                // Reflect the atom's velocity off the normal vector
                tvx = vx[i];
                tvy = vy[i];
                vx[i] = (tvx - 2 * (tvx * nx + tvy * ny) * nx);
                vy[i] = (tvy - 2 * (tvx * nx + tvy * ny) * ny);
              }
            }
          }
        },

        // ####################################################################
        // #         Functions calculating forces and accelerations.          #
        // ####################################################################

        // Calculate distance and force (if distance < cut-off distance).
        calculateLJInteraction = function(i, j) {
          var elI = element[i],
              elJ = element[j],
              dx  = x[j] - x[i],
              dy  = y[j] - y[i],
              rSq = sumSquare(dx, dy),
              fOverR, fx, fy;

          if (updateNeighborList && rSq < cutoffNeighborListSquared[elI][elJ]) {
            neighborList.markNeighbors(i, j);
          }

          // Don't calculate LJ interaction between bonded atoms. However note that bonded atoms
          // will be marked as neighbors during list update - it's necessary to avoid divergence
          // when the bond is removed.
          if (radialBondMatrix && radialBondMatrix[i] && radialBondMatrix[i][j]) return;

          if (rSq < cutoffDistance_LJ_sq[elI][elJ]) {
            fOverR = ljCalculator[elI][elJ].forceOverDistanceFromSquaredDistance(rSq);
            fx = fOverR * dx;
            fy = fOverR * dy;
            ax[i] += fx;
            ay[i] += fy;
            ax[j] -= fx;
            ay[j] -= fy;
          }
        },

        updateShortRangeForces = function () {
          // Fast path if Lennard Jones interaction is disabled.
          if (!useLennardJonesInteraction) return;

          if (updateNeighborList) {
            console.time('cell lists');
            shortRangeForcesCellList();
            console.timeEnd('cell lists');
          } else {
            console.time('neighbor list');
            shortRangeForcesNeighborList();
            console.timeEnd('neighbor list');
          }
        },

        shortRangeForcesCellList = function () {
          var rows = cellList.getRowsNum(),
              cols = cellList.getColsNum(),
              i, j, temp, cellIdx, cell1, cell2,
              a, b, atom1Idx, cell1Len, cell2Len,
              n, nLen, cellNeighbors;

          for (i = 0; i < rows; i++) {
            temp = i * cols;
            for (j = 0; j < cols; j++) {
              cellIdx = temp + j;

              cell1 = cellList.getCell(cellIdx);
              cellNeighbors = cellList.getNeighboringCells(i, j);

              for (a = 0, cell1Len = cell1.length; a < cell1Len; a++) {
                atom1Idx = cell1[a];

                // Interactions inside the cell.
                for (b = 0; b < a; b++) {
                  calculateLJInteraction(atom1Idx, cell1[b]);
                }
                // Interactions between neighboring cells.
                for (n = 0, nLen = cellNeighbors.length; n < nLen; n++) {
                  cell2 = cellNeighbors[n];
                  for (b = 0, cell2Len = cell2.length; b < cell2Len; b++) {
                    calculateLJInteraction(atom1Idx, cell2[b]);
                  }
                }
              }
            }
          }
        },

        shortRangeForcesNeighborList = function () {
          var nlist = neighborList.getList(),
              atom1Idx, atom2Idx, i, len;

          for (atom1Idx = 0; atom1Idx < N; atom1Idx++) {
            for (i = neighborList.getStartIdxFor(atom1Idx), len = neighborList.getEndIdxFor(atom1Idx); i < len; i++) {
              atom2Idx = nlist[i];
              calculateLJInteraction(atom1Idx, atom2Idx);
            }
          }
        },

        updateLongRangeForces = function() {
          // Fast path if Coulomb interaction is disabled or there are no charged atoms.
          if (!useCoulombInteraction || chargedAtomsList.length === 0) return;

          var i, j, len, dx, dy, rSq, fOverR, fx, fy,
              charge1, atom1Idx, atom2Idx,
              bondingPartners;

          for (i = 0, len = chargedAtomsList.length; i < len; i++) {
            atom1Idx = chargedAtomsList[i];
            charge1 = charge[atom1Idx];
            bondingPartners = radialBondMatrix && radialBondMatrix[atom1Idx];
            for (j = 0; j < i; j++) {
              atom2Idx = chargedAtomsList[j];
              if (bondingPartners && bondingPartners[atom2Idx]) continue;

              dx = x[atom2Idx] - x[atom1Idx];
              dy = y[atom2Idx] - y[atom1Idx];
              rSq = dx*dx + dy*dy;

              fOverR = coulomb.forceOverDistanceFromSquaredDistance(rSq, charge1, charge[atom2Idx],
                dielectricConst, realisticDielectricEffect);

              fx = fOverR * dx;
              fy = fOverR * dy;
              ax[atom1Idx] += fx;
              ay[atom1Idx] += fy;
              ax[atom2Idx] -= fx;
              ay[atom2Idx] -= fy;
            }
          }
        },

        updateFrictionForces = function() {
          if (!viscosity) return;

          var i,
              drag;

          for (i = 0; i < N; i++) {
            drag = viscosity * friction[i];

            ax[i] += (-vx[i] * drag);
            ay[i] += (-vy[i] * drag);
          }
        },

        updateRadialBondForces = function() {
          // fast path if no radial bonds have been defined
          if (N_radialBonds < 1) return;

          var i, i1, i2, dx, dy,
              rSq, r, k, r0,
              fOverR, fx, fy;

          for (i = 0; i < N_radialBonds; i++) {
            i1 = radialBondAtom1Index[i];
            i2 = radialBondAtom2Index[i];

            dx = x[i2] - x[i1];
            dy = y[i2] - y[i1];
            rSq = dx*dx + dy*dy;
            r = Math.sqrt(rSq);

            // eV/nm^2
            k = radialBondStrength[i];

            // nm
            r0 = radialBondLength[i];

            // "natural" Next Gen MW force units / nm
            fOverR = constants.convert(k*(r-r0), { from: unit.EV_PER_NM, to: unit.MW_FORCE_UNIT }) / r;

            fx = fOverR * dx;
            fy = fOverR * dy;

            ax[i1] += fx;
            ay[i1] += fy;
            ax[i2] -= fx;
            ay[i2] -= fy;
          }
        },

        updateAngularBondForces = function() {
          // Fast path if no angular bonds have been defined.
          if (N_angularBonds < 1) return;

          var i, i1, i2, i3,
              dxij, dyij, dxkj, dykj, rijSquared, rkjSquared, rij, rkj,
              k, angle, theta, cosTheta, sinTheta,
              forceInXForI, forceInYForI, forceInXForK, forceInYForK,
              commonPrefactor, temp;

          for (i = 0; i < N_angularBonds; i++) {
            i1 = angularBondAtom1Index[i];
            i2 = angularBondAtom2Index[i];
            i3 = angularBondAtom3Index[i];

            // radian
            angle = angularBondAngle[i];

            // (eV/nm * nm) / radian
            k = angularBondStrength[i];

            // Calculate angle (theta) between two vectors:
            // Atom1-Atom3 and Atom2-Atom3
            // Atom1 -> i, Atom2 -> k, Atom3 -> j
            dxij = x[i1] - x[i3];
            dxkj = x[i2] - x[i3];
            dyij = y[i1] - y[i3];
            dykj = y[i2] - y[i3];
            rijSquared = dxij * dxij + dyij * dyij;
            rkjSquared = dxkj * dxkj + dykj * dykj;
            rij = Math.sqrt(rijSquared);
            rkj = Math.sqrt(rkjSquared);
            // Calculate cos using dot product definition.
            cosTheta = (dxij * dxkj + dyij * dykj) / (rij * rkj);
            if (cosTheta > 1.0) cosTheta = 1.0;
            else if (cosTheta < -1.0) cosTheta = -1.0;
            // Pythagorean trigonometric identity.
            sinTheta = Math.sqrt(1.0 - cosTheta * cosTheta);
            // Finally:
            theta = Math.acos(cosTheta);

            if (sinTheta < 0.0001) sinTheta = 0.0001;

            // Calculate force.
            // "natural" Next Gen MW force units / nm
            commonPrefactor = constants.convert(k * (theta - angle) / (sinTheta * rij),
                { from: unit.EV_PER_NM, to: unit.MW_FORCE_UNIT }) / rkj;

            // nm^2
            temp = dxij * dxkj + dyij * dykj;
            // Terms in brackets end up with nm unit.
            // commonPrefactor is in "natural" Next Gen MW force units / nm,
            // so everything is correct.
            forceInXForI = commonPrefactor * (dxkj - temp * dxij / rijSquared);
            forceInYForI = commonPrefactor * (dykj - temp * dyij / rijSquared);
            forceInXForK = commonPrefactor * (dxij - temp * dxkj / rkjSquared);
            forceInYForK = commonPrefactor * (dyij - temp * dykj / rkjSquared);

            ax[i1] += forceInXForI;
            ay[i1] += forceInYForI;
            ax[i2] += forceInXForK;
            ay[i2] += forceInYForK;
            ax[i3] -= (forceInXForI + forceInXForK);
            ay[i3] -= (forceInYForI + forceInYForK);
          }
        },

        // FIXME: eliminate duplication with springForces
        updateRestraintForces = function() {
          // fast path if no restraints have been defined
          if (N_restraints < 1) return;

          var i,
              dx, dy,
              r, r_sq,
              k,
              f_over_r,
              fx, fy,
              a;

          for (i = 0; i < N_restraints; i++) {
            a = restraintAtomIndex[i];

            dx = restraintX0[i] - x[a];
            dy = restraintY0[i] - y[a];

            if (dx === 0 && dy === 0) continue;   // force will be zero

            r_sq = dx*dx + dy*dy;
            r = Math.sqrt(r_sq);

            // eV/nm^2
            k = restraintK[i];

            f_over_r = constants.convert(k*r, { from: unit.EV_PER_NM, to: unit.MW_FORCE_UNIT }) / r;

            fx = f_over_r * dx;
            fy = f_over_r * dy;

            ax[a] += fx;
            ay[a] += fy;
          }
        },

        updateSpringForces = function() {
          if (N_springForces < 1) return;

          var i,
              dx, dy,
              r, r_sq,
              k,
              f_over_r,
              fx, fy,
              a;

          for (i = 0; i < N_springForces; i++) {
            a = springForceAtomIndex[i];

            dx = springForceX[i] - x[a];
            dy = springForceY[i] - y[a];

            if (dx === 0 && dy === 0) continue;   // force will be zero

            r_sq = dx*dx + dy*dy;
            r = Math.sqrt(r_sq);

            // eV/nm^2
            k = springForceStrength[i];

            f_over_r = constants.convert(k*r, { from: unit.EV_PER_NM, to: unit.MW_FORCE_UNIT }) / r;

            fx = f_over_r * dx;
            fy = f_over_r * dy;

            ax[a] += fx;
            ay[a] += fy;
          }
        },

        // Returns center of mass of given atoms set (molecule).
        getMoleculeCenterOfMass = function (molecule) {
          var xcm = 0,
              ycm = 0,
              totalMass = 0,
              atomIdx, atomMass, i, len;

          for (i = 0, len = molecule.length; i < len; i++) {
            atomIdx = molecule[i];
            atomMass = mass[atomIdx];
            xcm += x[atomIdx] * atomMass;
            ycm += y[atomIdx] * atomMass;
            totalMass += atomMass;
          }
          xcm /= totalMass;
          ycm /= totalMass;
          return {x: xcm, y: ycm};
        },

        updateAminoAcidForces = function () {
          // Fast path if there is no solvent defined or it doesn't have impact on AAs.
          if (solventForceType === 0 || solventForceFactor === 0 || N < 2) return;

          var moleculeAtoms, atomIdx, cm, solventFactor,
              dx, dy, r, fx, fy, temp, i, j, len;

          // Reset helper array.
          for (i = 0; i < N; i++) {
            visited[i] = 0;
          }

          // Set multiplier of force produced by the solvent.
          // Constants used in Classic MW: 5 * 0.00001 = 0.00005.
          // Multiply it by 0.01 * 120 = 1.2 to convert from
          // 0.1A * 120amu / fs^2 to nm * amu / fs^2.
          // solventForceType is the same like in Classic MW (unitless).
          // solventForceFactor is a new variable used only in Next Gen MW.
          solventFactor = 0.00006 * solventForceType * solventForceFactor;

          for (i = 0; i < N; i++) {
            // Calculate forces only *once* for amino acid.
            if (visited[i] === 1) continue;

            moleculeAtoms = engine.getMoleculeAtoms(i);
            moleculeAtoms.push(i);

            cm = getMoleculeCenterOfMass(moleculeAtoms);

            for (j = 0, len = moleculeAtoms.length; j < len; j++) {
              atomIdx = moleculeAtoms[j];
              // Mark that atom was part of processed molecule to avoid
              // calculating its molecule again.
              visited[atomIdx] = 1;

              if (hydrophobicity[atomIdx] !== 0) {
                dx = x[atomIdx] - cm.x;
                dy = y[atomIdx] - cm.y;
                r = Math.sqrt(dx * dx + dy * dy);

                if (r > 0) {
                  temp = hydrophobicity[atomIdx] * solventFactor;

                  // AAs being pulled into the center of mass should feel an additional force factor that depends
                  // on distance from the center of mass, ranging between 1 and 25, with 1 being furthest away from the CoM
                  // and 25 being the max when at the CoM or within a certain radius of the CoM. In some ways this
                  // is closer to nature as the core of a protein is less exposed to solvent and thus even more stable.
                  if (temp > 0 && r < additionalSolventForceThreshold) {
                    // Force towards the center of mass, distance from the CoM less than a given threshold.
                    // Multiply force by an additional factor defined by the linear function of 'r' defined by two points:
                    // (0, additionalSolventForceMult) and (additionalSolventForceThreshold, 1).
                    temp *= (1 - additionalSolventForceMult) * r / additionalSolventForceThreshold + additionalSolventForceMult;
                  }

                  fx = temp * dx / r;
                  fy = temp * dy / r;
                  ax[atomIdx] -= fx;
                  ay[atomIdx] -= fy;
                }
              }
            }
          }
        },

        updateGravitationalAccelerations = function() {
          // fast path if there is no gravitationalField
          if (!gravitationalField) return;
          var i;

          for (i = 0; i < N; i++) {
            ay[i] -= gravitationalField;
          }
        },

        rectContains = function (i, x, y) {
          var rx = shapeX[i],
              ry = shapeY[i];
          return rx <= x && x <= rx + shapeWidth[i] &&
                 ry <= y && y <= ry + shapeHeight[i];
        },

        getElFieldForce = function (i) {
          var o = electricFieldOrientation[i];
          return (o === "N" || o === "E" ? 1 : -1) * electricFieldIntensity[i];
        },

        updateElectricFieldsAccelerations = function() {
          // fast path if there are no electric fields
          if (!electricFields) return;

          var i, e, o, vertical, rect, temp;

          for (e = 0; e < N_electricFields; e++) {
            o = electricFieldOrientation[e];
            vertical = o === "N" || o === "S";
            temp = getElFieldForce(e) / dielectricConst;
            rect = electricFieldShapeIdx[e];

            for (i = 0; i < N; i++) {
              if (rect != null && !rectContains(rect, x[i], y[i])) continue;
              if (vertical) {
                ay[i] += temp * charge[i] / mass[i];
              } else {
                ax[i] += temp * charge[i] / mass[i];
              }
            }
          }
        },

        // Push all amino acids above some Y coordinate during DNA translation.
        // TODO: this should be part of the MD2D plug-in for proteins engine!
        updateDNATranslationAccelerations = function() {
          if (!dnaTranslationInProgress) return;
          var i, diff;

          for (i = 0; i < N; i++) {
            diff = Math.min(1, 2.2 - y[i]);
            if (diff > 0) {
              ay[i] += 1e-4 * diff;
              ax[i] -= 3e-6;
            }
          }
        },

        // ####################################################################
        // #               Integration main helper functions.                 #
        // ####################################################################

        // For now, calculate only structures used by proteins engine.
        // TODO: move there calculation of various optimization structures like chargedAtomLists.
        calculateOptimizationStructures = function () {
          var cysteineEl = aminoacidsHelper.cysteineElement,
              idx, i;

          // Reset optimization data structure.
          freeCysteinesList.length = 0;

          for (i = 0; i < N; i++) {
            if (element[i] === cysteineEl) {
              // At the beginning, assume that each cysteine is "free" (ready to create disulfide bond).
              freeCysteinesList.push(i);
            }
          }

          for (i = 0; i < N_radialBonds; i++) {
            if (element[radialBondAtom1Index[i]] === cysteineEl && element[radialBondAtom2Index[i]] === cysteineEl) {
              // Two cysteines are already bonded, so remove them from freeCysteinsList.
              idx = freeCysteinesList.indexOf(radialBondAtom1Index[i]);
              if (idx !== -1) arrays.remove(freeCysteinesList, idx);
              idx = freeCysteinesList.indexOf(radialBondAtom2Index[i]);
              if (idx !== -1) arrays.remove(freeCysteinesList, idx);
            }
          }
        },

        // Accumulate acceleration into a(t + dt) from all possible interactions, fields
        // and forces connected with atoms.
        updateParticlesAccelerations = function () {
          var i, inverseMass;

          if (N === 0) return;

          // Zero out a(t) for accumulation of forces into a(t + dt).
          for (i = 0; i < N; i++) {
            ax[i] = ay[i] = 0;
          }

          // Check if the neighbor list should be recalculated.
          updateNeighborList = neighborList.shouldUpdate(x, y);

          if (updateNeighborList) {
            // Clear both lists.
            cellList.clear();
            neighborList.clear();

            for (i = 0; i < N; i++) {
              // Add particle to appropriate cell.
              cellList.addToCell(i, x[i], y[i]);
              // And save its initial position
              // ("initial" = position during neighbor list creation).
              neighborList.saveAtomPosition(i, x[i], y[i]);
            }
          }

          // ######################################
          // ax and ay are FORCES below this point
          // ######################################

          // Accumulate forces into a(t + dt) for all pairwise interactions between
          // particles:
          // Short-range forces (Lennard-Jones interaction).
          console.time('short-range forces');
          updateShortRangeForces();
          console.timeEnd('short-range forces');
          // Long-range forces (Coulomb interaction).
          console.time('long-range forces');
          updateLongRangeForces();
          console.timeEnd('long-range forces');

          // Accumulate forces from radially bonded interactions into a(t + dt).
          updateRadialBondForces();

          // Accumulate forces from angularly bonded interactions into a(t + dt).
          updateAngularBondForces();

          // Accumulate forces from restraint forces into a(t + dt).
          updateRestraintForces();

          // Accumulate forces from spring forces into a(t + dt).
          updateSpringForces();

          // Accumulate drag forces into a(t + dt).
          updateFrictionForces();

          // Apply forces caused by the hydrophobicity.
          // Affects only amino acids in the water or oil solvent.
          updateAminoAcidForces();

          // Convert ax, ay from forces to accelerations!
          for (i = 0; i < N; i++) {
            inverseMass = 1/mass[i];
            ax[i] *= inverseMass;
            ay[i] *= inverseMass;
          }

          // ############################################
          // ax and ay are ACCELERATIONS below this point
          // ############################################

          // Accumulate optional gravitational accelerations into a(t + dt).
          updateGravitationalAccelerations();

          // Accumulate optional accelerations coming from electric fields into a(t + dt).
          updateElectricFieldsAccelerations();

          // Push all amino acids above some Y coordinate during DNA translation.
          // TODO: this should be part of the MD2D plug-in for proteins engine!
          updateDNATranslationAccelerations();
        },

        // Half of the update of v(t + dt) and p(t + dt) using a. During a single integration loop,
        // call once when a = a(t) and once when a = a(t+dt).
        halfUpdateVelocity = function() {
          var i, m;
          for (i = 0; i < N; i++) {
            m = mass[i];
            vx[i] += 0.5 * ax[i] * dt;
            px[i] = m * vx[i];
            vy[i] += 0.5 * ay[i] * dt;
            py[i] = m * vy[i];
          }
        },

        // Calculate r(t + dt, i) from v(t + 0.5 * dt).
        updateParticlesPosition = function() {
          var width100  = size[0] * 100,
              height100 = size[1] * 100,
              xPrev, yPrev, i;

          for (i = 0; i < N; i++) {
            xPrev = x[i];
            yPrev = y[i];

            x[i] += vx[i] * dt;
            y[i] += vy[i] * dt;

            // Simple check if model has diverged. Prevents web browser from crashing.
            // isNaN tests not only x, y, but also vx, vy, ax, ay as test is done after
            // updateParticlesPosition(). If a displacement during one step is larger than width * 100
            // (or height * 100) it means that the velocity is far too big for the current time step.
            if (isNaN(x[i]) || isNaN(y[i]) ||
                Math.abs(x[i]) > width100 || Math.abs(y[i]) > height100) {
              throw new Error("Model has diverged!");
            }

            // Bounce off walls.
            bounceParticleOffWalls(i);
            // Bounce off obstacles, update pressure probes.
            bounceParticleOffObstacles(i, xPrev, yPrev, true);
            // Bounce off shapes
            bounceParticleOffShapes(i, xPrev, yPrev);
            // Bounce off lines
            bounceParticleOffLines(i, xPrev, yPrev);
          }
        },

        // Removes velocity and acceleration from pinned atoms.
        pinAtoms = function() {
          var i;

          for (i = 0; i < N; i++) {
            if (pinned[i]) {
              vx[i] = vy[i] = ax[i] = ay[i] = 0;
            }
          }
        },

        // Update speed using velocities.
        updateParticlesSpeed = function() {
          var i;

          for (i = 0; i < N; i++) {
            speed[i] = Math.sqrt(vx[i] * vx[i] + vy[i] * vy[i]);
          }
        },

        // Calculate new obstacles position using simple integration method.
        updateObstaclesPosition = function() {
          var ax, ay, vx, vy,
              drag, extFx, extFy, i;

          for (i = 0; i < N_obstacles; i++) {
            // Fast path when obstacle isn't movable.
            if (obstacleMass[i] === Infinity) continue;

            vx = obstacleVX[i];
            vy = obstacleVY[i];
            // External forces are defined per mass unit!
            // So, they are accelerations in fact.
            extFx = obstacleExtAX[i];
            extFy = obstacleExtAY[i];

            if (vx || vy || extFx || extFy || gravitationalField) {
              drag = viscosity * obstacleFriction[i];
              ax = extFx - drag * vx;
              ay = extFy - drag * vy - gravitationalField;

              obstacleXPrev[i] = obstacleX[i];
              obstacleYPrev[i] = obstacleY[i];

              // Update positions.
              obstacleX[i] += vx * dt + 0.5 * ax * dt_sq;
              obstacleY[i] += vy * dt + 0.5 * ay * dt_sq;

              // Update velocities.
              obstacleVX[i] += ax * dt;
              obstacleVY[i] += ay * dt;

              bounceObstacleOffWalls(i);
            }
          }
        },

        // Sets total momentum of each molecule to zero.
        // Useful for proteins engine.
        zeroTotalMomentumOfMolecules = function() {
          var moleculeAtoms, atomIdx, sumX, sumY, invMass,
              i, j, len;

          for (i = 0; i < N; i++) {
            visited[i] = 0;
          }

          for (i = 0; i < N; i++) {
            // Process each particular atom only *once*.
            if (visited[i] === 1) continue;

            moleculeAtoms = engine.getMoleculeAtoms(i);
            if (moleculeAtoms.length === 0) continue;
            moleculeAtoms.push(i);

            sumX = sumY = invMass = 0;
            for (j = 0, len = moleculeAtoms.length; j < len; j++) {
              atomIdx = moleculeAtoms[j];
              // Mark that atom was part of processed molecule to avoid
              // calculating its molecule again.
              visited[atomIdx] = 1;
              if (!pinned[atomIdx]) {
                sumX += vx[atomIdx] * mass[atomIdx];
                sumY += vy[atomIdx] * mass[atomIdx];
                invMass += mass[atomIdx];
              }
            }
            invMass = 1.0 / invMass;
            for (j = 0, len = moleculeAtoms.length; j < len; j++) {
              atomIdx = moleculeAtoms[j];
              if (!pinned[atomIdx]) {
                vx[atomIdx] -= sumX * invMass;
                vy[atomIdx] -= sumY * invMass;
                // Update momentum.
                px[atomIdx] = vx[atomIdx] * mass[atomIdx];
                py[atomIdx] = vy[atomIdx] * mass[atomIdx];
              }
            }
          }
        },

        adjustTemperature = function(target, forceAdjustment) {
          var rescalingFactor, i;

          if (target == null) target = T_target;

          T = computeTemperature();

          if (T === 0) {
            // Special case when T is 0.
            for (i = 0; i < N; i++) {
              if (pinned[i] === false) {
                // Add some random velocity to unpinned atoms.
                vx[i] = Math.random() * 0.02 - 0.01;
                vy[i] = Math.random() * 0.02 - 0.01;
              }
            }
            // Update temperature.
            T = computeTemperature();

            if (T === 0) {
              // This means that all atoms are pinned. Nothing to do.
              return;
            }
          }

          if (forceAdjustment || useThermostat && T > 0) {
            rescalingFactor = Math.sqrt(target / T);

            // Scale particles velocity.
            for (i = 0; i < N; i++) {
              vx[i] *= rescalingFactor;
              vy[i] *= rescalingFactor;
              px[i] *= rescalingFactor;
              py[i] *= rescalingFactor;
            }

            // Scale obstacles velocity.
            for (i = 0; i < N_obstacles; i++) {
              obstacleVX[i] *= rescalingFactor;
              obstacleVY[i] *= rescalingFactor;
            }

            T = target;
          }
        },

        // Two cysteine AAs can form a covalent bond between their sulphur atoms. We could model this such that
        // when two Cys AAs come close enough a covalent bond is formed (only one between a pair of cysteines).
        createDisulfideBonds = function () {
          var cys1Idx, cys2Idx, xDiff, yDiff, rSq, i, j, len;

          for (i = 0, len = freeCysteinesList.length; i < len; i++) {
            cys1Idx = freeCysteinesList[i];
            for (j = i + 1; j < len; j++) {
              cys2Idx = freeCysteinesList[j];

              xDiff = x[cys1Idx] - x[cys2Idx];
              yDiff = y[cys1Idx] - y[cys2Idx];
              rSq = xDiff * xDiff + yDiff * yDiff;

              // Check whether cysteines are close enough to each other.
              // As both are in the freeCysteinesList, they are not connected.
              if (rSq < 0.07) {
                // Connect cysteines.
                engine.addRadialBond({
                  atom1: cys1Idx,
                  atom2: cys2Idx,
                  length: Math.sqrt(rSq),
                  // Default strength of bonds between amino acids.
                  strength: 10000,
                  // Disulfide bond type.
                  type: 109
                });

                // Remove both cysteines from freeCysteinesList.
                arrays.remove(freeCysteinesList, i);
                arrays.remove(freeCysteinesList, j);

                // Update len, cys1Idx, j as freeCysteinesList has changed.
                // Not very pretty, but probably the fastest way.
                len = freeCysteinesList.length;
                cys1Idx = freeCysteinesList[i];
                j = i + 1;
              }
            }
          }
        },

        // ### Pressure calculation ###

        // Zero values of pressure probes. It should be called
        // at the beginning of the integration step.
        zeroPressureValues = function () {
          var i;
          for (i = 0; i < N_obstacles; i++) {
            if (obstacleNorthProbe[i]) {
              obstacleNProbeValue[i] = 0;
            }
            if (obstacleSouthProbe[i]) {
              obstacleSProbeValue[i] = 0;
            }
            if (obstacleEastProbe[i]) {
              obstacleEProbeValue[i] = 0;
            }
            if (obstacleWestProbe[i]) {
              obstacleWProbeValue[i] = 0;
            }
          }
        },

        // Update probes values so they contain final pressure value in Bar.
        // It should be called at the end of the integration step.
        calculateFinalPressureValues = function (duration) {
          var mult, i;
          // Classic MW converts impulses 2mv/dt to pressure in Bar using constant: 1666667.
          // See: the header of org.concord.mw2d.models.RectangularObstacle.
          // However, Classic MW also uses different units for mass and length:
          // - 120amu instead of 1amu,
          // - 0.1A instead of 1nm.
          // We should convert mass, velocity and obstacle height to Next Gen units.
          // Length units reduce themselves (velocity divided by height or width), only mass is left.
          // So, divide classic MW constant 1666667 by 120 - the result is 13888.89.
          // [ There is unit module available, however for reduction of computational cost,
          // include conversion in the pressure constant, especially considering the fact that
          // conversion from 120amu to amu is quite simple. ]
          mult = 13888.89 / duration;
          for (i = 0; i < N_obstacles; i++) {
            if (obstacleNorthProbe[i]) {
              obstacleNProbeValue[i] *= mult / obstacleWidth[i];
            }
            if (obstacleSouthProbe[i]) {
              obstacleSProbeValue[i] *= mult / obstacleWidth[i];
            }
            if (obstacleEastProbe[i]) {
              obstacleEProbeValue[i] *= mult / obstacleHeight[i];
            }
            if (obstacleWestProbe[i]) {
              obstacleWProbeValue[i] *= mult / obstacleHeight[i];
            }
          }
        },

        // Removes all angular bonds that includes atom1 and atom2 as one of the "arms".
        // Function is useful when e.g. radial bond is removed.
        removeAngularBondsContaining = function (atom1, atom2, conserveEnergy) {
          var i;
          // Use such "strange" form of loop, as while removing one bonds,
          // other change their indexing. So, after removal of bond 5, we
          // should check bond 5 again, as it would be another bond (previously
          // indexed as 6).
          i = 0;
          while (i < N_angularBonds) {
            // Remove related angular bonds.
            if ((angularBondAtom1Index[i] === atom1 && angularBondAtom3Index[i] === atom2) ||
                (angularBondAtom1Index[i] === atom2 && angularBondAtom3Index[i] === atom1) ||
                (angularBondAtom2Index[i] === atom1 && angularBondAtom3Index[i] === atom2) ||
                (angularBondAtom2Index[i] === atom2 && angularBondAtom3Index[i] === atom1))
              engine.removeAngularBond(i, conserveEnergy);
            else
              i++;
          }
        };

    // A list of the indices of atoms having nonzero charge.
    // (Yes, this introduces some slightly different code patterns than are used elsewhere here, as
    // it's probably time to evolve away from this-avoidance and the onevar style.)
    var chargedAtomsList = [];
    chargedAtomsList.reset = function() {
      var i, j = 0;
      for (i = 0; i < N; i++) {
        if (atoms.charge[i]) {
          this[j++] = i;
        }
      }
      this.length = j;
    };

    // radialBondMatrix[i][j] === true when atoms i and j are "radially bonded"
    // radialBondMatrix[i][j] === undefined otherwise
    var radialBondMatrix = [];
    radialBondMatrix.reset = function () {
      var i, atom1, atom2;

      this.length = 0;

      for (i = 0; i < N_radialBonds; i++) {
        atom1 = radialBondAtom1Index[i];
        atom2 = radialBondAtom2Index[i];
        this[atom1] = this[atom1] || [];
        this[atom1][atom2] = true;
        this[atom2] = this[atom2] || [];
        this[atom2][atom1] = true;
      }
    };

    // bondedAtoms[i] contains a list of atoms bonded to atom "i".
    var bondedAtoms = [];
    bondedAtoms.reset = function () {
      var i, atom1, atom2;

      this.length = 0;

      for (i = 0; i < N_radialBonds; i++) {
        atom1 = radialBondAtom1Index[i];
        atom2 = radialBondAtom2Index[i];
        this[atom1] = this[atom1] || [];
        this[atom1].push(atom2);
        this[atom2] = this[atom2] || [];
        this[atom2].push(atom1);
      }
    };
    bondedAtoms.unset = function(atom1, atom2) {
      if (this[atom1]) {
        var atom1Idx = this[atom2].indexOf(atom1);
        var atom2Idx = this[atom1].indexOf(atom2);
        if (atom1Idx !== -1) {
          this[atom1].splice(atom2Idx, 1);
          this[atom2].splice(atom1Idx, 1);
        }
      }
    };
    bondedAtoms.set = function(atom1, atom2) {
      if (this[atom1] == null) {
        this[atom1] = [];
      }
      if (this[atom2] == null) {
        this[atom2] = [];
      }
      this[atom1].push(atom2);
      this[atom2].push(atom1);
    };

    // ####################################################################
    // ####################################################################

    engine = {

      // Adds a new plugin. Plugin will be initialized with the object arrys, so that
      // it can add to them as necessary, and will then be registered in the controller,
      // allowing it to respond to functions passed to the controller from arbitrary
      // points in the md2d code.
      addPlugin: function(plugin) {
        if (plugin.initialize) {
          // plugins can update the data arrays as needed so we pass in the arrays.
          // we do this as an object, so we can add new arrays as needed by the plugins
          // without needing to update all existing plugins
          plugin.initialize({
            atoms: atoms,
            elements: elements,
            radialBonds: radialBonds,
            angularBonds: angularBonds
          });
        }

        pluginController.registerPlugin(plugin);
      },

      useCoulombInteraction: function(v) {
        useCoulombInteraction = !!v;
      },

      useLennardJonesInteraction: function(v) {
        useLennardJonesInteraction = !!v;
      },

      useThermostat: function(v) {
        useThermostat = !!v;
      },

      setVDWLinesRatio: function(vdwlr) {
        if (typeof vdwlr === "number" && vdwlr !== 0) {
          vdwLinesRatio = vdwlr;
        }
      },

      setGravitationalField: function(gf) {
        if (typeof gf === "number" && gf !== 0) {
          gravitationalField = gf;
        } else {
          gravitationalField = false;
        }
      },

      setTemperatureOfAtoms: function(atomIndices, targetT) {

        var i, j, vxtmp, vytmp, smallT, smallKE, scale, s, groupT,
            nGroup = atomIndices.length;

        // Assign a random direction and speed to atoms with velocity exactly equal to 0 (e.g.
        // cooled drastically or newly created). This ensures that we don't just rescale the
        // velocities of the (possibly small or nonexistent) group of atoms that already have some
        // velocity. After rescaling, the net effect is to transfer some velocity from moving atoms
        // to non-moving atoms.

        // Pick a small temperature to assign to non-moving atoms
        smallT = (computeTemperatureOfAtoms(atomIndices) || targetT) * 0.0001;
        smallKE = convertTtoKE(smallT, 1);

        // Assign moveable, non-moving atoms a small temperature
        for (i = 0; i < nGroup; i++) {
          j = atomIndices[i];
          if (!pinned[j] && vx[j] === 0 && vy[j] === 0) {
            vxtmp = Math.random() - 0.5;
            vytmp = Math.random() - 0.5;
            s  = Math.sqrt( (2*smallKE/mass[j]) / (vxtmp*vxtmp + vytmp*vytmp) );
            vx[j] = vxtmp * s;
            vy[j] = vytmp * s;
          }
        }

        T      = computeTemperature();
        groupT = computeTemperatureOfAtoms(atomIndices);

        scale = Math.sqrt( targetT / groupT );

        for (i = 0; i < nGroup; i++) {
          j = atomIndices[i];
          engine.setAtomProperties(j, {
            vx: vx[j] * scale,
            vy: vy[j] * scale
          });
        }
      },

      getTemperatureOfAtoms: function(atomIndices) {
        return computeTemperatureOfAtoms(atomIndices);
      },

      setTargetTemperature: function(v) {
        validateTemperature(v);
        T_target = v;
      },

      setDielectricConstant: function(dc) {
        dielectricConst = dc;
      },

      setRealisticDielectricEffect: function (r) {
        realisticDielectricEffect = r;
      },

      setSolventForceType: function(sft) {
        solventForceType = sft;
      },

      setDNAState: function (s) {
        // Don't store DNAState, it's not necessary. Just
        // information whether translation is in progress is useful.
        dnaTranslationInProgress = s.indexOf("translation:") === 0;
      },

      setSolventForceFactor: function(sff) {
        solventForceFactor = sff;
      },

      setAdditionalSolventForceMult: function(asfm) {
        additionalSolventForceMult = asfm;
      },

      setAdditionalSolventForceThreshold: function(asft) {
        additionalSolventForceThreshold = asft;
      },

      // Our timekeeping is really a convenience for users of this lib, so let them reset time at will
      setTime: function(t) {
        time = t;
      },

      setDimensions: function(v) {
        // NB. We may want to create a simple state diagram for the md engine (as well as for the 'modeler' defined in
        // lab.molecules.js)
        if (sizeHasBeenInitialized) {
          throw new Error("The molecular model's size has already been set, and cannot be reset.");
        }
        minX = v[0];
        minY = v[1];
        maxX = v[2];
        maxY = v[3];
        size = [maxX - minX, maxY - minY];
        sizeHasBeenInitialized = true;
      },

      getDimensions: function() {
        return [minX, minY, maxX, maxY];
      },

      getLJCalculator: function() {
        return ljCalculator;
      },

      setAtomProperties: function (i, props) {
        var cysteineEl = aminoacidsHelper.cysteineElement,
            key, amino, j;

        if (props.element !== undefined) {
          if (props.element < 0 || props.element >= N_elements) {
            throw new Error("md2d: Unknown element " + props.element + ", an atom can't be created.");
          }

          // Special case when cysteine AA is morphed into other AA type,
          // which can't create disulphide bonds. Remove a connected
          // disulphide bond if it exists.
          if (element[i] === cysteineEl && props.element !== cysteineEl) {
            for (j = 0; j < N_radialBonds; j++) {
              if ((radialBondAtom1Index[j] === i || radialBondAtom2Index[j] === i) &&
                   radialBondType[j] === 109) {
                // Remove the radial bond representing disulphide bond.
                engine.removeRadialBond(j);
                // One cysteine can create only one disulphide bond so there is no need to continue the loop.
                break;
              }
            }
          }

          // Mark element as used by some atom (used by performance optimizations).
          elementUsed[props.element] = true;

          // Update mass and radius when element is changed.
          props.mass   = elementMass[props.element];
          props.radius = elementRadius[props.element];

          if (aminoacidsHelper.isAminoAcid(props.element)) {
            amino = aminoacidsHelper.getAminoAcidByElement(props.element);
            // Setup properties which are relevant to amino acids.
            props.charge = amino.charge;
            // Note that we overwrite value set explicitly in the hash.
            // So, while setting element of atom, it's impossible to set also its charge.
            props.hydrophobicity = amino.hydrophobicity;
          }
        }

        // Update charged atoms list (performance optimization).
        if (!charge[i] && props.charge) {
          // !charge[i]   => shortcut for charge[i] === 0 || charge[i] === undefined (both cases can occur).
          // props.charge => shortcut for props.charge !== undefined && props.charge !== 0.
          // Save index of charged atom.
          chargedAtomsList.push(i);
        } else if (charge[i] && props.charge === 0) {
          // charge[i] => shortcut for charge[i] !== undefined && charge[i] !== 0 (both cases can occur).
          // Remove index from charged atoms list.
          chargedAtomsList.splice(chargedAtomsList.indexOf(i), 1);
        }

        // Set all properties from props hash.
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            atoms[key][i] = props[key];
          }
        }

        // Update properties which depend on other properties.
        px[i]    = vx[i] * mass[i];
        py[i]    = vy[i] * mass[i];
        speed[i] = Math.sqrt(vx[i] * vx[i] + vy[i] * vy[i]);
      },

      setRadialBondProperties: function(i, props, conserveAngularBondsEnergy) {
        var key, atom1, atom2;

        // Unset current radial bond matrix entry.
        // Matrix will be updated when new properties are set.
        atom1 = radialBondAtom1Index[i];
        atom2 = radialBondAtom2Index[i];
        if (radialBondMatrix[atom1] && radialBondMatrix[atom1][atom2])
          radialBondMatrix[atom1][atom2] = false;
        if (radialBondMatrix[atom2] && radialBondMatrix[atom2][atom1])
          radialBondMatrix[atom2][atom1] = false;

        bondedAtoms.unset(atom1, atom2);

        // If atom1 or atom2 properties are changed, remove related angular bonds.
        if ((props.atom1 !== undefined && props.atom1 !== atom1) ||
            (props.atom2 !== undefined && props.atom2 !== atom2)) {
          removeAngularBondsContaining(atom1, atom2, conserveAngularBondsEnergy);
        }

        // Set all properties from props hash.
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            radialBonds[key][i] = props[key];
          }
        }

        // Update radial bond matrix.
        atom1 = radialBondAtom1Index[i];
        atom2 = radialBondAtom2Index[i];
        if (!radialBondMatrix[atom1]) radialBondMatrix[atom1] = [];
        radialBondMatrix[atom1][atom2] = true;
        if (!radialBondMatrix[atom2]) radialBondMatrix[atom2] = [];
        radialBondMatrix[atom2][atom1] = true;

        bondedAtoms.set(atom1, atom2);
      },

      setAngularBondProperties: function(i, props) {
        var key;
        // Set all properties from props hash.
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            angularBonds[key][i] = props[key];
          }
        }
      },

      setRestraintProperties: function(i, props) {
        var key;
        // Set all properties from props hash.
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            restraints[key][i] = props[key];
          }
        }
      },

      setElementProperties: function(i, properties) {
        var j, newRadius;
        // FIXME we cached mass into its own array, which is now probably unnecessary (position-update
        // calculations have since been speeded up by batching the computation of accelerations from
        // forces.) If we remove the mass[] array we also remove the need for the loop below:

        if (properties.mass != null && properties.mass !== elementMass[i]) {
            elementMass[i] = properties.mass;
          for (j = 0; j < N; j++) {
            if (element[j] === i) mass[j] = properties.mass;
          }
        }

        if (properties.sigma != null) {
          elementSigma[i] = properties.sigma;
          newRadius = lennardJones.radius(properties.sigma);

          if (elementRadius[i] !== newRadius) {
            elementRadius[i] = newRadius;
            for (j = 0; j < N; j++) {
              if (element[j] === i) radius[j] = newRadius;
            }
          }
        }

        if (properties.epsilon != null) elementEpsilon[i] = properties.epsilon;

        if (properties.color != null) {
          elementColor[i] = properties.color;
        }

        for (j = 0; j < N_elements; j++) {
          setPairwiseLJProperties(i, j);
        }
        // Reinitialize optimization structures, as sigma can be changed.
        initializeCellList();
        initializeNeighborList();
      },

      setPairwiseLJProperties: function (i, j) {
        // Call private (closure) version of this funcion.
        setPairwiseLJProperties(i, j);
        // Reinitialize optimization structures, as sigma can be changed.
        initializeCellList();
        initializeNeighborList();
      },

      setObstacleProperties: function (i, props) {
        var key;

        if (!engine.canPlaceObstacle(props.x, props.y, props.width, props.height, i))
          throw new Error("Obstacle can't be placed at " + props.x + ", " + props.y);

        // If position is manually changed, update previous
        // position also.
        if (props.x !== undefined) {
          props.xPrev = props.x;
        }
        if (props.y !== undefined) {
          props.yPrev = props.y;
        }
        // Try to parse mass, as it may be string "Infinity".
        if (typeof props.mass === 'string') {
          props.mass = parseFloat(props.mass);
        }

        // Set properties from props hash.
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            obstacles[key][i] = props[key];
          }
        }
      },

      setShapeProperties: function (i, props) {
        var key;
        // Set properties from props hash.
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            shapes[key][i] = props[key];
          }
        }
      },

      setLineProperties: function (i, props) {
        var key;
        // Set properties from props hash.
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            lines[key][i] = props[key];
          }
        }
      },

      setElectricFieldProperties: function (i, props) {
        var key;
        // Set properties from props hash.
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            electricFields[key][i] = props[key];
          }
        }
      },

      /**
        The canonical method for adding an atom to the collections of atoms.

        If there isn't enough room in the 'atoms' array, it (somewhat inefficiently)
        extends the length of the typed arrays by ten to have room for more atoms.

        @returns the index of the new atom
      */
      addAtom: function(props) {
        if (N + 1 > atoms.x.length) {
          utils.extendArrays(atoms, Math.round(N * 1.5 + 10));
          assignShortcutReferences.atoms();
        }

        // Set acceleration of new atom to zero.
        props.ax = props.ay = 0;

        // Remove any stray value from charge--setAtomProperties updates chargedAtomsList based on
        // whether the atom was charged previously.
        charge[N] = 0;

        // Set provided properties of new atom.
        engine.setAtomProperties(N, props);

        // Increase number of atoms.
        N++;

        // Initialize helper structures for optimizations.
        initializeCellList();
        initializeNeighborList();
      },

      removeAtom: function(idx) {
        var i, len, prop,
            l, list, lists;

        if (idx >= N) {
          throw new Error("Atom " + idx + " doesn't exist, so it can't be removed.");
        }

        // Start from removing all bonds connected to this atom.
        // Note that we are removing only radial bonds. Angular bonds
        // will be removed while removing radial bond, not atom!

        // Use such "strange" form of loop, as while removing one bonds,
        // other change their indexing. So, after removal of bond 5, we
        // should check bond 5 again, as it would be another bond (previously
        // indexed as 6).
        i = 0;
        while (i < N_radialBonds) {
          if (radialBondAtom1Index[i] === idx || radialBondAtom2Index[i] === idx)
            engine.removeRadialBond(i);
          else
            i++;
        }

        // Try to remove atom from charged atoms list.
        i = chargedAtomsList.indexOf(idx);
        if (i !== -1) {
          arrays.remove(chargedAtomsList, i);
        }

        // Finally, remove atom.

        // Shift atoms properties and zero last element.
        // It can be optimized by just replacing the last
        // atom with atom 'i', however this approach
        // preserves more expectable atoms indexing.
        for (i = idx; i < N; i++) {
          for (prop in atoms) {
            if (atoms.hasOwnProperty(prop)) {
              if (i === N - 1)
                atoms[prop][i] = 0;
              else
                atoms[prop][i] = atoms[prop][i + 1];
            }
          }
        }

        // Update number of atoms!
        N--;

        // Shift indices of atoms in various lists.
        lists = [
          chargedAtomsList,
          radialBondAtom1Index, radialBondAtom2Index,
          angularBondAtom1Index, angularBondAtom2Index, angularBondAtom3Index
        ];

        for (l = 0; l < lists.length; l++) {
          list = lists[l];
          for (i = 0, len = list.length; i < len; i++) {
            if (list[i] > idx)
              list[i]--;
          }
        }

        // Recalculate radial bond matrix and bonded atoms lists, as indices have changed.
        radialBondMatrix.reset();
        bondedAtoms.reset();

        // (Re)initialize helper structures for optimizations.
        initializeCellList();
        initializeNeighborList();

        neighborList.invalidate();

        // Update accelerations of atoms.
        updateParticlesAccelerations();
      },

      /**
        The canonical method for adding an element.
      */
      addElement: function(props) {
        var i;

        if (N_elements >= elementEpsilon.length) {
          utils.extendArrays(elements, N_elements + 10);
          assignShortcutReferences.elements();
        }

        elementMass[N_elements]    = props.mass;
        elementEpsilon[N_elements] = props.epsilon;
        elementSigma[N_elements]   = props.sigma;
        elementRadius[N_elements]  = lennardJones.radius(props.sigma);
        elementColor[N_elements]   = props.color;

        ljCalculator[N_elements]              = [];
        cutoffDistance_LJ_sq[N_elements]      = [];
        cutoffNeighborListSquared[N_elements] = [];

        for (i = 0; i <= N_elements; i++) {
          setPairwiseLJProperties(N_elements, i);
        }
        // Note that we don't have to reinitialize optimization
        // structures (cell lists and neighbor list). They are
        // based only on the properties of *used* elements, so
        // adding a new atom should trigger reinitialization instead.

        N_elements++;
      },

      /**
        The canonical method for adding a radial bond to the collection of radial bonds.
      */
      addRadialBond: function(props) {
        if (N_radialBonds + 1 > radialBondAtom1Index.length) {
          utils.extendArrays(radialBonds, N_radialBonds + 10);
          assignShortcutReferences.radialBonds();
        }

        N_radialBonds++;

        // Set new radial bond properties.
        engine.setRadialBondProperties(N_radialBonds - 1, props);

        radialBondsChanged = true;
      },

      removeRadialBond: function(idx, conserveAngularBondsEnergy) {
        var i, prop;

        if (idx >= N_radialBonds) {
          throw new Error("Radial bond " + idx + " doesn't exist, so it can't be removed.");
        }

        // Start from removing angular bonds.
        removeAngularBondsContaining(radialBondAtom1Index[idx], radialBondAtom2Index[idx], conserveAngularBondsEnergy);

        // Update optimization structure.
        bondedAtoms.unset(radialBondAtom1Index[idx], radialBondAtom2Index[idx]);

        // Shift radial bonds properties and zero last element.
        // It can be optimized by just replacing the last
        // radial bond with radial bond 'i', however this approach
        // preserves more expectable indexing.
        // TODO: create some general function for that, as it's duplicated
        // in each removeObject method.
        for (i = idx; i < N_radialBonds; i++) {
          for (prop in radialBonds) {
            if (radialBonds.hasOwnProperty(prop)) {
              if (i === N_radialBonds - 1)
                radialBonds[prop][i] = 0;
              else
                radialBonds[prop][i] = radialBonds[prop][i + 1];
            }
          }
        }

        N_radialBonds--;

        // Recalculate radial bond matrix as bond indices have changed.
        radialBondMatrix.reset();

        radialBondsChanged = true;
      },

      /**
        The canonical method for adding an 'restraint' bond to the collection of restraints.

        If there isn't enough room in the 'restraints' array, it (somewhat inefficiently)
        extends the length of the typed arrays by ten to have room for more bonds.
      */
      addRestraint: function(props) {
        if (N_restraints + 1 > restraints.atomIndex.length) {
          utils.extendArrays(restraints, N_restraints + 10);
          assignShortcutReferences.restraints();
        }

        N_restraints++;

        // Set new restraint properties.
        engine.setRestraintProperties(N_restraints - 1, props);
      },

      /**
        The canonical method for adding an angular bond to the collection of angular bonds.

        If there isn't enough room in the 'angularBonds' array, it (somewhat inefficiently)
        extends the length of the typed arrays by ten to have room for more bonds.
      */
      addAngularBond: function(props) {
        if (N_angularBonds + 1 > angularBonds.atom1.length) {
          utils.extendArrays(angularBonds, N_angularBonds + 10);
          assignShortcutReferences.angularBonds();
        }

        N_angularBonds++;

        // Set new angular bond properties.
        engine.setAngularBondProperties(N_angularBonds - 1, props);
      },

      removeAngularBond: function(idx, conserveEnergy) {
        var i, prop, a1, a2, a3, angleDiff;

        if (idx >= N_angularBonds) {
          throw new Error("Angular bond " + idx + " doesn't exist, so it can't be removed.");
        }

        if (conserveEnergy) {
          a1 = angularBondAtom1Index[idx];
          a2 = angularBondAtom2Index[idx];
          a3 = angularBondAtom3Index[idx];
          angleDiff = angularBondAngle[idx] - math.getAngleBetweenVec(x[a1], y[a1],
                                                                      x[a2], y[a2],
                                                                      x[a3], y[a3]);
          engine.addKEToAtoms(0.5 * angularBondStrength[idx] * angleDiff * angleDiff, a1, a2, a3);
        }

        // Shift angular bonds properties and zero last element.
        // It can be optimized by just replacing the last
        // angular bond with angular bond 'i', however this approach
        // preserves more expectable indexing.
        // TODO: create some general function for that, as it's duplicated
        // in each removeObject method.
        for (i = idx; i < N_angularBonds; i++) {
          for (prop in angularBonds) {
            if (angularBonds.hasOwnProperty(prop)) {
              if (i === N_angularBonds - 1)
                angularBonds[prop][i] = 0;
              else
                angularBonds[prop][i] = angularBonds[prop][i + 1];
            }
          }
        }

        N_angularBonds--;
      },

      /**
        Adds a spring force between an atom and an x, y location.

        @returns the index of the new spring force.
      */
      addSpringForce: function(atomIndex, x, y, strength) {
        // conservatively just add one spring force
        if (N_springForces + 1 > springForces[0].length) {
          utils.extendArrays(springForces, N_springForces + 1);
          assignShortcutReferences.springForces();
        }

        springForceAtomIndex[N_springForces]  = atomIndex;
        springForceX[N_springForces]          = x;
        springForceY[N_springForces]          = y;
        springForceStrength[N_springForces]   = strength;

        return N_springForces++;
      },

      updateSpringForce: function(i, x, y) {
        springForceX[i] = x;
        springForceY[i] = y;
      },

      removeSpringForce: function(idx) {
        var i, j;

        if (idx >= N_springForces) {
          throw new Error("Spring force " + idx + " doesn't exist, so it can't be removed.");
        }

        N_springForces--;

        // Shift spring forces properties.
        for (i = idx; i < N_springForces; i++) {
          for (j = 0; j < 4; j++) {
            springForces[j][i] = springForces[j][i + 1];
          }
        }
      },

      springForceAtomIndex: function(i) {
        return springForceAtomIndex[i];
      },

      addObstacle: function(props) {
        if (!engine.canPlaceObstacle(props.x, props.y, props.width, props.height))
          throw new Error("Obstacle can't be placed at " + props.x + ", " + props.y + ".");

        if (N_obstacles + 1 > obstacles.x.length) {
          // Extend arrays each time (as there are only
          // a few obstacles in typical model).
          utils.extendArrays(obstacles, N_obstacles + 1);
          assignShortcutReferences.obstacles();
        }

        N_obstacles++;

        // Set properties of new obstacle.
        engine.setObstacleProperties(N_obstacles - 1, props);
      },

      removeObstacle: function(idx) {
        var i, prop;

        if (idx >= N_obstacles) {
          throw new Error("Obstacle " + idx + " doesn't exist, so it can't be removed.");
        }

        N_obstacles--;

        // Shift obstacles properties.
        // It can be optimized by just replacing the last
        // obstacle with obstacle 'i', however this approach
        //  preserves more expectable obstacles indexing.
        for (i = idx; i < N_obstacles; i++) {
          for (prop in obstacles) {
            if (obstacles.hasOwnProperty(prop)) {
              obstacles[prop][i] = obstacles[prop][i + 1];
            }
          }
        }

        // FIXME: This shouldn't be necessary, however various modules
        // (e.g. views) use obstacles.x.length as the real number of obstacles.
        utils.extendArrays(obstacles, N_obstacles);
        assignShortcutReferences.obstacles();
      },

      addShape: function(props) {
        if (N_shapes + 1 > shapes.x.length) {
          // Extend arrays each time (as there are only
          // a few shapes in typical model).
          utils.extendArrays(shapes, N_shapes + 1);
          assignShortcutReferences.shapes();
        }

        N_shapes++;

        // Set properties of new shape.
        engine.setShapeProperties(N_shapes - 1, props);
      },

      removeShape: function(idx) {
        var i, prop;

        if (idx >= N_shapes) {
          throw new Error("Shape " + idx + " doesn't exist, so it can't be removed.");
        }

        // Remove all electric fields connected with this shape.

        // Use such "strange" form of loop, as while removing one electric field,
        // other change their indexing. So, after removal of field 5, we
        // should check field 5 again, as it would be another field (previously
        // indexed as 6).
        i = 0;
        while (i < N_electricFields) {
          if (electricFieldShapeIdx[i] === idx)
            engine.removeElectricField(i);
          else
            i++;
        }

        N_shapes--;

        // Shift shapes properties.
        // It can be optimized by just replacing the last
        // shape with shape 'i', however this approach
        //  preserves more expectable shapes indexing.
        for (i = idx; i < N_shapes; i++) {
          for (prop in shapes) {
            if (shapes.hasOwnProperty(prop)) {
              shapes[prop][i] = shapes[prop][i + 1];
            }
          }
        }

        // Shift indices of shapes referenced by electric fields.
        for (i = 0; i < N_electricFields; i++) {
          if (electricFieldShapeIdx[i] > idx)
            electricFieldShapeIdx[i]--;
        }

        // FIXME: This shouldn't be necessary, however various modules
        // (e.g. views) use shapes.x.length as the real number of shapes.
        utils.extendArrays(shapes, N_shapes);
        assignShortcutReferences.shapes();
      },

      addLine: function(props) {
        if (N_lines + 1 > lines.x1.length) {
          // Extend arrays each time (as there are only
          // a few lines in typical model).
          utils.extendArrays(lines, N_lines + 1);
          assignShortcutReferences.lines();
        }

        N_lines++;

        // Set properties of new line.
        engine.setLineProperties(N_lines - 1, props);
      },

      removeLine: function(idx) {
        var i, prop;

        if (idx >= N_lines) {
          throw new Error("Line " + idx + " doesn't exist, so it can't be removed.");
        }

        // Shift lines properties.
        // It can be optimized by just replacing the last
        // shape with shape 'i', however this approach
        //  preserves more expectable lines indexing.
        for (i = idx; i < N_lines; i++) {
          for (prop in lines) {
            if (lines.hasOwnProperty(prop)) {
              lines[prop][i] = lines[prop][i + 1];
            }
          }
        }

        N_lines--;

        // FIXME: This shouldn't be necessary, however various modules
        // (e.g. views) use lines.x1.length as the real number of lines.
        utils.extendArrays(lines, N_lines);
        assignShortcutReferences.lines();
      },

      addElectricField: function(props) {
        if (N_electricFields + 1 > electricFields.intensity.length) {
          // Extend arrays each time (as there are only
          // a few electricFields in typical model).
          utils.extendArrays(electricFields, N_electricFields + 1);
          assignShortcutReferences.electricFields();
        }

        N_electricFields++;

        // Set properties of new shape.
        engine.setElectricFieldProperties(N_electricFields - 1, props);
      },

      removeElectricField: function(idx) {
        var i, prop;

        if (idx >= N_electricFields) {
          throw new Error("Electric field " + idx + " doesn't exist, so it can't be removed.");
        }

        N_electricFields--;

        // Shift electric fields properties.
        for (i = idx; i < N_electricFields; i++) {
          for (prop in electricFields) {
            if (electricFields.hasOwnProperty(prop)) {
              electricFields[prop][i] = electricFields[prop][i + 1];
            }
          }
        }

        // Follow convention of other engine objects whose array is reduced after removal.
        utils.extendArrays(electricFields, N_electricFields);
        assignShortcutReferences.electricFields();
      },

      atomInBounds: function(_x, _y, i) {
        var r = radius[i], j;

        if (_x < r || _x > size[0] - r || _y < r || _y > size[1] - r) {
          return false;
        }
        for (j = 0; j < N_obstacles; j++) {
          if (_x > (obstacleX[j] - r) && _x < (obstacleX[j] + obstacleWidth[j] + r) &&
              _y > (obstacleY[j] - r) && _y < (obstacleY[j] + obstacleHeight[j] + r)) {
            return false;
          }
        }
        return true;
      },

      /**
        Checks to see if an uncharged atom could be placed at location x,y
        without increasing the PE (i.e. overlapping with another atom), and
        without being on an obstacle or past a wall.

        Optionally, an atom index i can be included which will tell the function
        to ignore the existance of atom i. (Used when moving i around.)
      */
      canPlaceAtom: function(element, _x, _y, i, skipPECheck) {
        var orig_x,
            orig_y,
            PEAtLocation,
            testX, testY, testXMax, testYMax,
            j;

        // first do the simpler check to see if we're outside the walls
        if ( !engine.atomInBounds(_x, _y, i) ) {
          return false;
        }

        // Check collision with obstacles.
        for (j = 0; j < N_obstacles; j++) {
          testX = obstacleX[j];
          testY = obstacleY[j];
          testXMax = testX + obstacleWidth[j];
          testYMax = testY + obstacleHeight[j];
          if ((_x > testX && _x < testXMax) &&
              (_y > testY && _y < testYMax)) {
            return false;
          }
        }

        if (skipPECheck) {
          return true;
        }

        // then check PE at location
        if (typeof i === "number") {
          orig_x = x[i];
          orig_y = y[i];
          x[i] = y[i] = Infinity;   // move i atom away
        }

        PEAtLocation = engine.newPotentialCalculator(element, 0, false)(_x, _y);

        if (typeof i === "number") {
          x[i] = orig_x;
          y[i] = orig_y;
        }

        return PEAtLocation <= 0;
      },

      /**
        Checks to see if an obstacle could be placed at location x, y
        without being on an atom, another obstacle or past a wall.

        idx parameter is optional. It should be defined and equal to id
        of an existing obstacle when the existing obstacle should be checked.
        It prevents an algorithm from comparing the obstacle with itself during
        collisions detection.
      */
      canPlaceObstacle: function (obsX, obsY, obsWidth, obsHeight, idx) {
        var obsXMax = obsX + obsWidth,
            obsYMax = obsY + obsHeight,
            testX, testY, testXMax, testYMax,
            r, i;

        // Check collision with walls.
        if (obsX < 0 || obsXMax > size[0] || obsY < 0 || obsYMax > size[0]) {
          return false;
        }

        // Check collision with atoms.
        for (i = 0; i < N; i++) {
          r = radius[i];
          if (x[i] > (obsX - r) && x[i] < (obsXMax + r) &&
              y[i] > (obsY - r) && y[i] < (obsYMax + r)) {
            return false;
          }
        }

        // Check collision with other obstacles.
        for (i = 0; i < N_obstacles; i++) {
          if (idx !== undefined && idx === i) {
            // If we are checking existing obstacle,
            // avoid comparing it with itself.
            continue;
          }
          testX = obstacleX[i];
          testY = obstacleY[i];
          testXMax = testX + obstacleWidth[i];
          testYMax = testY + obstacleHeight[i];
          if ((obsXMax > testX && obsX < testXMax) &&
              (obsYMax > testY && obsY < testYMax)) {
            return false;
          }
        }

        return true;
      },

      setupAtomsRandomly: function(options) {

        var // if a temperature is not explicitly requested, we just need any nonzero number
            temperature = options.temperature || 100,

            nrows = Math.floor(Math.sqrt(N)),
            ncols = Math.ceil(N/nrows),

            i, r, c, rowSpacing, colSpacing,
            vMagnitude, vDirection, props;

        validateTemperature(temperature);

        colSpacing = size[0] / (1 + ncols);
        rowSpacing = size[1] / (1 + nrows);

        // Arrange molecules in a lattice. Not guaranteed to have CM exactly on center, and is an artificially low-energy
        // configuration. But it works OK for now.
        i = -1;

        for (r = 1; r <= nrows; r++) {
          for (c = 1; c <= ncols; c++) {
            i++;
            if (i === N) break;
            vMagnitude = math.normal(1, 1/4);
            vDirection = 2 * Math.random() * Math.PI;

            props = {
              element: Math.floor(Math.random() * options.userElements), // random element
              x:       c * colSpacing,
              y:       r * rowSpacing,
              vx:      vMagnitude * Math.cos(vDirection),
              vy:      vMagnitude * Math.sin(vDirection),
              charge:  2 * (i % 2) - 1 // alternate negative and positive charges
            };
            engine.setAtomProperties(i, props);
          }
        }

        // now, remove all translation of the center of mass and rotation about the center of mass
        computeCMMotion();
        removeTranslationAndRotationFromVelocities();

        // Scale randomized velocities to match the desired initial temperature.
        //
        // Note that although the instantaneous temperature will be 'temperature' exactly, the temperature will quickly
        // settle to a lower value because we are initializing the atoms spaced far apart, in an artificially low-energy
        // configuration.
        //
        adjustTemperature(temperature, true);
      },

      getVdwPairsArray: function() {
        var i,
            j,
            dx,
            dy,
            r_sq,
            x_i,
            y_i,
            sigma_i,
            epsilon_i,
            sigma_j,
            epsilon_j,
            index_i,
            index_j,
            sig,
            eps,
            distanceCutoff_sq = vdwLinesRatio * vdwLinesRatio;

        N_vdwPairs = 0;

        for (i = 0; i < N; i++) {
          // pairwise interactions
          index_i = element[i];
          sigma_i   = elementSigma[index_i];
          epsilon_i = elementSigma[index_i];
          x_i = x[i];
          y_i = y[i];

          for (j = i+1; j < N; j++) {
            if (N_radialBonds !== 0 && (radialBondMatrix[i] && radialBondMatrix[i][j])) continue;

            index_j = element[j];
            sigma_j   = elementSigma[index_j];
            epsilon_j = elementSigma[index_j];

            if (charge[i]*charge[j] <= 0) {
              dx = x[j] - x_i;
              dy = y[j] - y_i;
              r_sq = dx*dx + dy*dy;


              sig = 0.5 * (sigma_i+sigma_j);
              sig *= sig;
              eps = epsilon_i * epsilon_j;

              if (r_sq < sig * distanceCutoff_sq && eps > 0) {
                if (N_vdwPairs + 1 > vdwPairs.atom1.length) {
                  utils.extendArrays(vdwPairs, (N_vdwPairs + 1) * 2);
                  assignShortcutReferences.vdwPairs();
                }
                vdwPairAtom1Index[N_vdwPairs] = i;
                vdwPairAtom2Index[N_vdwPairs] = j;
                N_vdwPairs++;
              }
            }
          }
        }

        vdwPairs.count = N_vdwPairs;
        return vdwPairs;
      },

      // Velocity Verlet integration scheme.
      // See: http://en.wikipedia.org/wiki/Verlet_integration#Velocity_Verlet
      // The current implementation is:
      // 1. Calculate: v(t + 0.5 * dt) = v(t) + 0.5 * a(t) * dt
      // 2. Calculate: r(t + dt) = r(t) + v(t + 0.5 * dt) * dt
      // 3. Derive a(t + dt) from the interaction potential using r(t + dt)
      // 4. Calculate: v(t + dt) = v(t + 0.5 * dt) + 0.5 * a(t + dt) * dt
      integrate: function(duration, _dt) {
        var steps, iloop, tStart = time;

        // How much time to integrate over, in fs.
        if (duration === undefined)  duration = 100;

        // The length of an integration timestep, in fs.
        if (_dt === undefined) _dt = 1;

        dt = _dt;        // dt is a closure variable that helpers need access to
        dt_sq = dt * dt; // the squared time step is also needed by some helpers.

        // Clear flag indicating if some radial bonds were added or removed during the integration
        // step.
        radialBondsChanged = false;

        // Prepare optimization structures to ensure that they are valid during integration.
        // Note that when user adds or removes various objects (like atoms, bonds), such structures
        // can become invalid. That's why we update them each time before integration.
        // It's also safer and easier to do recalculate each structure than to modify it while
        // engine state is changed by user.
        calculateOptimizationStructures();

        // Calculate accelerations a(t), where t = 0.
        // Later this is not necessary, as a(t + dt) from
        // previous step is used as a(t) in the current step.
        if (time === 0) {
          updateParticlesAccelerations();
        }

        // Number of steps.
        steps = Math.floor(duration / dt);

        // Zero values of pressure probes at the beginning of
        // each integration step.
        zeroPressureValues();

        // Clear the acceleration and velocity for pinned atoms, as we may have updated
        // acceleration prior to entering integrate() (e.g. in readModelState).
        pinAtoms();

        for (iloop = 1; iloop <= steps; iloop++) {
          time = tStart + iloop * dt;

          // Calculate v(t + 0.5 * dt) using v(t) and a(t).
          halfUpdateVelocity();

          // Update r(t + dt) using v(t + 0.5 * dt).
          updateParticlesPosition();

          // Accumulate accelerations into a(t + dt) from all possible interactions, fields
          // and forces connected with atoms.
          updateParticlesAccelerations();

          // Clear the acceleration and velocity for pinned atoms before moving them.
          pinAtoms();

          // Calculate v(t + dt) using v(t + 0.5 * dt) and a(t + dt).
          halfUpdateVelocity();

          // Now that we have velocity v(t + dt), update speed.
          updateParticlesSpeed();

          // Move obstacles using very simple integration.
          updateObstaclesPosition();

          // If solvent is different from vacuum (water or oil), ensure that
          // the total momentum of each molecule is equal to zero. This
          // prevents amino acids chains from drifting towards one boundary of
          // the model. Don't do it during translation process to let the protein
          // freely fold.
          if (solventForceType !== 0 && !dnaTranslationInProgress) {
            zeroTotalMomentumOfMolecules();
          }

          pluginController.callPluginFunction('performActionWithinIntegrationLoop', [neighborList, dt, time]);

          // Adjust temperature, e.g. when heat bath is enabled.
          adjustTemperature();
        } // end of integration loop

        // Collisions between particles and obstacles are collected during
        // updateParticlesPosition() execution. This function takes into account
        // time which passed and converts raw data from pressure probes to value
        // in Bars.
        calculateFinalPressureValues(duration);

        // After each integration loop try to create new disulfide bonds between cysteines.
        // It's enough to do it outside the inner integration loop (performance).
        createDisulfideBonds();
      },

      updateParticlesAccelerations: updateParticlesAccelerations,

      // Minimize energy using steepest descend method.
      minimizeEnergy: function () {
            // Maximal length of displacement during one step of minimization.
        var stepLength   = 1e-3,
            // Maximal acceleration allowed.
            accThreshold = 1e-4,
            // Maximal number of iterations allowed.
            iterLimit    = 3000,
            maxAcc, delta, xPrev, yPrev, i, iter;

        // Calculate accelerations.
        updateParticlesAccelerations();
        pinAtoms();
        // Get maximum value.
        maxAcc = 0;
        for (i = 0; i < N; i++) {
          if (maxAcc < Math.abs(ax[i]))
            maxAcc = Math.abs(ax[i]);
          if (maxAcc < Math.abs(ay[i]))
            maxAcc = Math.abs(ay[i]);
        }

        iter = 0;
        while (maxAcc > accThreshold && iter < iterLimit) {
          iter++;

          delta = stepLength / maxAcc;
          for (i = 0; i < N; i++) {
            xPrev = x[i];
            yPrev = y[i];
            x[i] += ax[i] * delta;
            y[i] += ay[i] * delta;

            // Keep atoms in bounds.
            bounceParticleOffWalls(i);
            // Bounce off obstacles, but DO NOT update pressure probes.
            bounceParticleOffObstacles(i, xPrev, yPrev, false);
            // Bounce off shapes
            bounceParticleOffShapes(i, xPrev, yPrev);
            // Bounce off lines
            bounceParticleOffLines(i, xPrev, yPrev);
          }

          // Calculate accelerations.
          updateParticlesAccelerations();
          pinAtoms();
          // Get maximum value.
          maxAcc = 0;
          for (i = 0; i < N; i++) {
            if (maxAcc < Math.abs(ax[i]))
              maxAcc = Math.abs(ax[i]);
            if (maxAcc < Math.abs(ay[i]))
              maxAcc = Math.abs(ay[i]);
          }
        }
      },

      getRadialBondsForAtom: function(index) {
        var rbonds = [],
            i,
            i1,
            i2;

        for (i = 0; i < N_radialBonds; i++) {
          i1 = radialBondAtom1Index[i];
          i2 = radialBondAtom2Index[i];
          if (index === i1 || index === i2) {
            rbonds.push(i);
          }
        }
        return rbonds;
      },

      getAngularBondsForAtom: function(index) {
        var abonds = [],
            i,
            i1,
            i2,
            i3;

        for (i = 0; i < N_angularBonds; i++) {
          i1 = angularBondAtom1Index[i];
          i2 = angularBondAtom2Index[i];
          i3 = angularBondAtom3Index[i];
          if (index === i1 || index === i2 || index === i3) {
            abonds.push(i);
          }
        }
        return abonds;
      },

      // Total mass of all particles in the system, in Dalton (atomic mass units).
      getTotalMass: function() {
        var totalMass = 0, i;
        for (i = 0; i < N; i++) {
          totalMass += mass[i];
        }
        return totalMass;
      },

      getRadiusOfElement: function(el) {
        return elementRadius[el];
      },

      getNumberOfAtoms: function() {
        return N;
      },

      getNumberOfElements: function() {
        return N_elements;
      },

      getNumberOfObstacles: function() {
        return N_obstacles;
      },

      getNumberOfShapes: function() {
        return N_shapes;
      },

      getNumberOfLines: function() {
        return N_lines;
      },

      getNumberOfRadialBonds: function() {
        return N_radialBonds;
      },

      getNumberOfAngularBonds: function() {
        return N_angularBonds;
      },

      getNumberOfRestraints: function() {
        return N_restraints;
      },

      getNumberOfSpringForces: function() {
        return N_springForces;
      },

      getNumberOfElectricFields: function() {
        return N_electricFields;
      },

      /**
        Compute the model state and store into the passed-in 'state' object.
        (Avoids GC hit of throwaway object creation.)
      */
      // TODO: [refactoring] divide this function into smaller chunks?
      computeOutputState: function(state) {
        var i, j, e,
            i1, i2, i3,
            el1, el2,
            dx, dy,
            dxij, dyij, dxkj, dykj,
            cosTheta, theta, rect,
            r_sq, rij, rkj,
            k, dr, angleDiff,
            elInMWUnits,
            gravPEInMWUnits,
            // Total kinetic energy, in MW units.
            KEinMWUnits,
            // Potential energy, in eV.
            PE;

        // Calculate potentials in eV. Note that we only want to do this once per call to integrate(), not once per
        // integration loop!
        PE = 0;
        KEinMWUnits = 0;

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

          // gravitational PE
          if (gravitationalField) {
            gravPEInMWUnits = mass[i] * gravitationalField * y[i];
            PE += constants.convert(gravPEInMWUnits, { from: unit.MW_ENERGY_UNIT, to: unit.EV });
          }

          // electric field PE
          for (e = 0; e < N_electricFields; e++) {
            rect = electricFieldShapeIdx[e];
            if (rect != null && !rectContains(rect, x[i], y[i])) continue;
            elInMWUnits = charge[i] * getElFieldForce(e);
            switch (electricFieldOrientation[e]) {
            case "N":
            case "S":
              elInMWUnits *= (rect != null ? shapeY[rect] : minY) - y[i]; break;
            case "W":
            case "E":
              elInMWUnits *= (rect != null ? shapeX[rect] : minX) - x[i]; break;
            }
            PE += constants.convert(elInMWUnits, { from: unit.MW_ENERGY_UNIT, to: unit.EV });
          }

          KEinMWUnits += 0.5 * mass[i] * (vx[i] * vx[i] + vy[i] * vy[i]);

          // pairwise interactions
          for (j = i+1; j < N; j++) {
            dx = x[j] - x[i];
            dy = y[j] - y[i];

            r_sq = dx*dx + dy*dy;

            // FIXME the signs here don't really make sense
            if (useLennardJonesInteraction) {
              PE -=ljCalculator[element[i]][element[j]].potentialFromSquaredDistance(r_sq);
            }
            if (useCoulombInteraction && chargedAtomsList.length > 0) {
              PE += coulomb.potential(Math.sqrt(r_sq), charge[i], charge[j], dielectricConst, realisticDielectricEffect);
            }
          }
        }

        // radial bonds
        for (i = 0; i < N_radialBonds; i++) {
          i1 = radialBondAtom1Index[i];
          i2 = radialBondAtom2Index[i];
          el1 = element[i1];
          el2 = element[i2];

          dx = x[i2] - x[i1];
          dy = y[i2] - y[i1];
          r_sq = dx*dx + dy*dy;

          // eV/nm^2
          k = radialBondStrength[i];

          // nm
          dr = Math.sqrt(r_sq) - radialBondLength[i];

          PE += 0.5*k*dr*dr;

          // Remove the Lennard Jones potential for the bonded pair
          if (useLennardJonesInteraction) {
            PE += ljCalculator[el1][el2].potentialFromSquaredDistance(r_sq);
          }
          if (useCoulombInteraction && charge[i1] && charge[i2]) {
            PE -= coulomb.potential(Math.sqrt(r_sq), charge[i1], charge[i2], dielectricConst, realisticDielectricEffect);
          }
        }

        // Angular bonds.
        for (i = 0; i < N_angularBonds; i++) {
          i1 = angularBondAtom1Index[i];
          i2 = angularBondAtom2Index[i];
          i3 = angularBondAtom3Index[i];

          // Calculate angle (theta) between two vectors:
          // Atom1-Atom3 and Atom2-Atom3
          // Atom1 -> i, Atom2 -> k, Atom3 -> j
          dxij = x[i1] - x[i3];
          dxkj = x[i2] - x[i3];
          dyij = y[i1] - y[i3];
          dykj = y[i2] - y[i3];
          rij = Math.sqrt(dxij * dxij + dyij * dyij);
          rkj = Math.sqrt(dxkj * dxkj + dykj * dykj);
          // Calculate cos using dot product definition.
          cosTheta = (dxij * dxkj + dyij * dykj) / (rij * rkj);
          if (cosTheta > 1.0) cosTheta = 1.0;
          else if (cosTheta < -1.0) cosTheta = -1.0;
          theta = Math.acos(cosTheta);

          // Finally, update PE.
          // radian
          angleDiff = theta - angularBondAngle[i];
          // angularBondStrength unit: eV/radian^2
          PE += 0.5 * angularBondStrength[i] * angleDiff * angleDiff;
        }

        // update PE for 'restraint' bonds
        for (i = 0; i < N_restraints; i++) {
          i1 = restraintAtomIndex[i];
          el1 = element[i1];

          dx = restraintX0[i] - x[i1];
          dy = restraintY0[i] - y[i1];
          r_sq = dx*dx + dy*dy;

          // eV/nm^2
          k = restraintK[i];

          // nm
          dr = Math.sqrt(r_sq);

          PE += 0.5*k*dr*dr;
       }

        // Process all obstacles.
        for (i = 0; i < N_obstacles; i++) {

          if (obstacleMass[i] !== Infinity) {
            // Gravitational potential energy.
            if (gravitationalField) {
              gravPEInMWUnits = obstacleMass[i] * gravitationalField * obstacleY[i];
              PE += constants.convert(gravPEInMWUnits, { from: unit.MW_ENERGY_UNIT, to: unit.EV });
            }
            // Kinetic energy.
            KEinMWUnits += 0.5 * obstacleMass[i] *
                (obstacleVX[i] * obstacleVX[i] + obstacleVY[i] * obstacleVY[i]);
          }
        }

        // Update temperature.
        T = convertKEtoT(KEinMWUnits, N);

        // "macro" state
        state.time           = time;
        state.PE             = PE;
        state.KE             = constants.convert(KEinMWUnits, { from: unit.MW_ENERGY_UNIT, to: unit.EV });
        state.temperature    = T;
        state.pCM            = [px_CM, py_CM]; // TODO: GC optimization? New array created each time.
        state.CM             = [x_CM, y_CM];
        state.vCM            = [vx_CM, vy_CM];
        state.omega_CM       = omega_CM;

        // "micro" state. TODO: put radial bonds, etc here.
        // TODO2: do we really need to put all objects here? Can't modeler ask about interesting
        // arrays using just some getter or property, e.g.: engine.getAtoms() or engine.atoms?
        state.atoms = atoms;
        state.radialBonds = radialBonds;

        // Let plugins modify output state, e.g. PE, KE etc.
        pluginController.callPluginFunction('processOutputState', [state]);
      },


      /**
        Given a test element and charge, returns a function that returns for a location (x, y) in nm:
         * the potential energy, in eV, of an atom of that element and charge at location (x, y)
         * optionally, if calculateGradient is true, the gradient of the potential as an
           array [gradX, gradY]. (units: eV/nm)
      */
      newPotentialCalculator: function(testElement, testCharge, calculateGradient) {

        return function(testX, testY) {
          var PE = 0,
              fx = 0,
              fy = 0,
              gradX,
              gradY,
              ljTest = ljCalculator[testElement],
              i,
              dx,
              dy,
              r_sq,
              r,
              f_over_r,
              lj;

          for (i = 0; i < N; i++) {
            dx = testX - x[i];
            dy = testY - y[i];
            r_sq = dx*dx + dy*dy;
            f_over_r = 0;

            if (useLennardJonesInteraction) {
              lj = ljTest[element[i]];
              PE += -lj.potentialFromSquaredDistance(r_sq, testElement, element[i]);
              if (calculateGradient) {
                f_over_r += lj.forceOverDistanceFromSquaredDistance(r_sq);
              }
            }

            if (useCoulombInteraction && chargedAtomsList.length > 0 && testCharge) {
              r = Math.sqrt(r_sq);
              PE += -coulomb.potential(r, testCharge, charge[i], dielectricConst, realisticDielectricEffect);
              if (calculateGradient) {
                f_over_r += coulomb.forceOverDistanceFromSquaredDistance(r_sq, testCharge, charge[i],
                  dielectricConst, realisticDielectricEffect);
              }
            }

            if (f_over_r) {
              fx += f_over_r * dx;
              fy += f_over_r * dy;
            }
          }

          if (calculateGradient) {
            gradX = constants.convert(fx, { from: unit.MW_FORCE_UNIT, to: unit.EV_PER_NM });
            gradY = constants.convert(fy, { from: unit.MW_FORCE_UNIT, to: unit.EV_PER_NM });
            return [PE, [gradX, gradY]];
          }

          return PE;
        };
      },


      // Adds a kinetic energy (defined in eV) to a group of atoms defined as optional arguments.
      // e.g. .addKEToAtoms(2, 1, 2, 3) will add 2eV to atoms 1, 2 and 3. When no atom index is
      // specified, the function will use all available atoms.
      // Returns false when it's impossible (e.g. it can happen when provided energy is negative
      // and atoms can't be cooled down more), true otherwise.
      addKEToAtoms: function(energyChange) {
        var atoms = Array.prototype.slice.call(arguments, 1),
            oldKE = 0,
            newKE,
            ratio,
            i, len, idx;

        if (atoms.length === 0) {
          for (i = 0; i < N; i++) {
            atoms.push(i);
          }
        }

        for (i = 0, len = atoms.length; i < len; i++) {
          oldKE += engine.getAtomKineticEnergy(atoms[i]);
        }

        newKE = oldKE + energyChange;

        if (newKE <= 0) {
          // Energy can't be conserved using a given set of atoms.
          return false;
        }
        if (oldKE === 0) {
          idx = atoms[0];
          vx[idx] = Math.random() * 2 - 1 * 1e-5;
          vy[idx] = Math.random() * 2 - 1 * 1e-5;
          oldKE = engine.getAtomKineticEnergy(idx);
        }

        ratio = Math.sqrt(newKE / oldKE);

        for (i = 0, len = atoms.length; i < len; i++) {
          idx = atoms[i];
          vx[idx] *= ratio;
          vy[idx] *= ratio;
          // TODO: probably we shouldn't store (px, py) at all, but calculate it when needed.
          px[idx] *= ratio;
          py[idx] *= ratio;
        }

        return true;
      },

      addKEToAtomPairAndConserveMomentum: function(deltaKE, atom1, atom2) {

        // rotate into x-axis to simplify the quadratic equation below and to avoid numerical
        // problems when dx is small

        var dx = x[atom2] - x[atom1];
        var dy = y[atom2] - y[atom1];
        var theta = -Math.atan2(dy, dx);
        var cosTheta = Math.cos(theta);
        var sinTheta = Math.sin(theta);

        var v1x = cosTheta * vx[atom1] - sinTheta * vy[atom1];
        var v2x = cosTheta * vx[atom2] - sinTheta * vy[atom2];
        var v1y = sinTheta * vx[atom1] + cosTheta * vy[atom1];
        var v2y = sinTheta * vx[atom2] + cosTheta * vy[atom2];

        var m1 = mass[atom1];
        var m2 = mass[atom2];

        // use MW units here
        var oldKE = 0.5*m1*(v1x*v1x + v1y*v1y) + 0.5*m2*(v2x*v2x + v2y*v2y);
        var newKE = oldKE + constants.convert(deltaKE, { from: unit.EV, to: unit.MW_ENERGY_UNIT });

        if (newKE < 0) {
          return false;
        }

        // We require m1 * dv1x = -m2 * dv2x (momentum conservation)
        // and m1 * ((v1x + dv1)^2 + v1y^2) + m2 * ((v2x + dv2x)^2 + v2y^2) = 2 * newKE
        // This results in a quadratic equation in dv1x; solve it:
        var a = m1 + m1*m1/m2;
        var b = 2 * m1 * (v1x - v2x);
        var c = 2 * (oldKE - newKE);
        var disc = b*b - 4*a*c;

        if (disc < 0) {
          // Momentum can't be conserved.
          return false;
        }

        // Of the two roots, choose the solution that minimizes the changes to the individual
        // momenta (which are equal and opposite, so we only need to check v1x)
        disc = Math.sqrt(disc);
        var dv1xPlus = (-b + disc) / (2*a);
        var dv1xMinus = (-b - disc) / (2*a);
        var dv1x = Math.abs(dv1xPlus) < Math.abs(dv1xMinus) ? dv1xPlus : dv1xMinus;

        v1x += dv1x;
        v2x += (-m1/m2 * dv1x);

        // reverse sign of rotation
        sinTheta *= -1;

        vx[atom1] = v1x * cosTheta - v1y * sinTheta;
        px[atom1] = m1 * vx[atom1];
        vy[atom1] = v1x * sinTheta + v1y * cosTheta;
        py[atom1] = m1 * vy[atom1];

        vx[atom2] = v2x * cosTheta - v2y * sinTheta;
        px[atom2] = m2 * vx[atom2];
        vy[atom2] = v2x * sinTheta + v2y * cosTheta;
        py[atom2] = m2 * vy[atom2];

        return true;
      },

      /**
        Starting at (x,y), try to find a position which minimizes the potential energy change caused
        by adding at atom of element el.
      */
      findMinimumPELocation: function(el, x, y, charge) {
        var pot    = engine.newPotentialCalculator(el, charge, true),
            radius = elementRadius[el],

            res =  math.minimize(pot, [x, y], {
              bounds: [ [radius, size[0]-radius], [radius, size[1]-radius] ]
            });

        if (res.error) return false;
        return res[1];
      },

      /**
        Starting at (x,y), try to find a position which minimizes the square of the potential energy
        change caused by adding at atom of element el, i.e., find a "farthest from everything"
        position.
      */
      findMinimumPESquaredLocation: function(el, x, y, charge) {
        var pot = engine.newPotentialCalculator(el, charge, true),

            // squared potential energy, with gradient
            potsq = function(x,y) {
              var res, f, grad;

              res = pot(x,y);
              f = res[0];
              grad = res[1];

              // chain rule
              grad[0] *= (2*f);
              grad[1] *= (2*f);

              return [f*f, grad];
            },

            radius = elementRadius[el],

            res = math.minimize(potsq, [x, y], {
              bounds: [ [radius, size[0]-radius], [radius, size[1]-radius] ],
              stopval: 1e-4,
              precision: 1e-6
            });

        if (res.error) return false;
        return res[1];
      },

      /**
        Returns all atoms in the same molecule as atom idx
        (not including idx itself)
      */
      getMoleculeAtoms: function(idx) {
        // Use simple DFS algorithm.
        var result = [];
        var stack = [];
        var visited = {};
        var bondedAtoms, bondedAtom, i, len;

        stack.push(idx);
        visited[idx] = true;
        while (stack.length > 0) {
          bondedAtoms = engine.getBondedAtoms(stack.pop());
          for (i = 0, len = bondedAtoms.length; i < len; i++) {
            bondedAtom = bondedAtoms[i];
            if (!visited[bondedAtom]) {
              visited[bondedAtom] = true;
              stack.push(bondedAtom);
              result.push(bondedAtom);
            }
          }
        }
        return result;
      },

      /**
        Returns all atoms directly bonded to atom idx
      */
      getBondedAtoms: function(idx) {
        return bondedAtoms[idx] || [];
      },

      getCoulombForceAt: function(testX, testY, resultObj) {
        // Let client code reuse objects.
        resultObj = resultObj || {};
        // Fast path if Coulomb interaction is disabled or there are no charged atoms.
        if (!useCoulombInteraction || chargedAtomsList.length === 0) {
          resultObj.fx = resultObj.fy = 0;
          return resultObj;
        }

        var fx = 0, fy = 0,
            i, len, dx, dy, rSq, fOverR, atomCharge, atomIdx, rect, o;

        for (i = 0, len = chargedAtomsList.length; i < len; i++) {
          atomIdx = chargedAtomsList[i];
          atomCharge = charge[atomIdx];

          dx = x[atomIdx] - testX;
          dy = y[atomIdx] - testY;
          rSq = dx * dx + dy * dy;

          fOverR = coulomb.forceOverDistanceFromSquaredDistance(rSq, 1, atomCharge,
            dielectricConst, realisticDielectricEffect);

          fx += fOverR * dx;
          fy += fOverR * dy;
        }

        for (i = 0; i < N_electricFields; i++) {
          rect = electricFieldShapeIdx[i];
          if (rect != null && !rectContains(rect, testX, testY)) continue;
          o = electricFieldOrientation[i];
          if (o === "N" || o === "S") {
            fy += getElFieldForce(i); // * 1 Coulomb (test charge)
          } else {
            fx += getElFieldForce(i); // * 1 Coulomb (test charge)
          }
        }


        resultObj.fx = constants.convert(fx, { from: unit.MW_FORCE_UNIT, to: unit.EV_PER_NM });
        resultObj.fy = constants.convert(fy, { from: unit.MW_FORCE_UNIT, to: unit.EV_PER_NM });
        return resultObj;
      },

      /**
        Returns Kinetic Energy of single atom i, in eV.
      */
      getAtomKineticEnergy: function(i) {
        var KEinMWUnits = 0.5 * mass[i] * (vx[i] * vx[i] + vy[i] * vy[i]);
        return constants.convert(KEinMWUnits, { from: unit.MW_ENERGY_UNIT, to: unit.EV });
      },

      getAtomNeighbors: function(idx) {
        var res = [],
            list = neighborList.getList(),
            i, len;

        for (i = neighborList.getStartIdxFor(idx), len = neighborList.getEndIdxFor(idx); i < len; i++) {
          res.push(list[i]);
        }
        return res;
      },

      getNeighborList: function () {
        return neighborList;
      },

      setViscosity: function(v) {
        viscosity = v;
      },

      get ljCalculator() {
        return ljCalculator;
      },

      /**
        Indicates whether some radial bonds were added or removed during the last integration step.
        This flag is cleared at the beginning of the integration.
       */
      get radialBondsChanged() {
        return radialBondsChanged;
      },

      /**
       * Returns true when atoms i and j are "radially bonded", false otherwise.
       */
      atomsBonded: function(i, j) {
        return !!(radialBondMatrix && radialBondMatrix[i] && radialBondMatrix[i][j]);
      },

      // ######################################################################
      //                State definition of the engine

      // Return array of objects defining state of the engine.
      // Each object in this list should implement following interface:
      // * .clone()        - returning complete state of that object.
      // * .restore(state) - restoring state of the object, using 'state'
      //                     as input (returned by clone()).
      getState: function() {
        var state = [
          // Use wrapper providing clone-restore interface to save the hashes-of-arrays
          // that represent model state.
          new CloneRestoreWrapper(elements),
          new CloneRestoreWrapper(atoms),
          new CloneRestoreWrapper(obstacles),
          new CloneRestoreWrapper(shapes),
          new CloneRestoreWrapper(lines),
          new CloneRestoreWrapper(radialBonds),
          new CloneRestoreWrapper(angularBonds),
          new CloneRestoreWrapper(restraints),
          new CloneRestoreWrapper(springForces),
          // PairwiseLJProperties class implements Clone-Restore Interface.
          pairwiseLJProperties,

          // Also save toplevel state (time, number of atoms, etc):
          {
            clone: function () {
              return {
                time          : time,
                N             : N,
                N_elements    : N_elements,
                N_obstacles   : N_obstacles,
                N_shapes      : N_shapes,
                N_lines       : N_lines,
                N_radialBonds : N_radialBonds,
                N_angularBonds: N_angularBonds,
                N_restraints  : N_restraints,
                N_springForces: N_springForces
              };
            },
            restore: function(state) {
              time           = state.time;
              N              = state.N;
              N_elements     = state.N_elements;
              N_shapes       = state.N_shapes;
              N_lines        = state.N_lines;
              N_radialBonds  = state.N_radialBonds;
              N_angularBonds = state.N_angularBonds;
              N_restraints   = state.N_restraints;
              N_springForces = state.N_springForces;

              neighborList.invalidate();
              radialBondMatrix.reset();
              bondedAtoms.reset();
              chargedAtomsList.reset();
            }
          }
        ];

        pluginController.callPluginFunction('getState', [], function(pluginState) {
          state = state.concat(pluginState);
        });

        return state;
      },

      // FIXME. Not a sustainable pattern. This is just a temporary pass-through of modeler-level
      // methods that are implemented in the quantumDynamics plugin, because for now the plugin is
      // only callable from the engine.
      callPluginAccessor: function(accessorMethodName, args) {
        var returnValue;
            args = args || [];
        pluginController.callPluginFunction(accessorMethodName, args, function(_) {
          returnValue = _;
        });
        return returnValue;
      }
    };



    // Initialization
    initialize();

    // Export initialized objects to Public API.
    // To ensure that client code always has access to these public properties,
    // they should be initialized  only once during the engine lifetime (in the initialize method).
    engine.pairwiseLJProperties = pairwiseLJProperties;

    // Finally, return Public API.
    return engine;
  };
});

/*global define: false */

// Definitions of the default MD2D units. Every model property exposed by md2d/models/modeler.js is
// in one of the unit types below.

// This particular set of definitions is for reference and for generating the correct labels on
// output properties; it's not used for computation. Unit conversions that need to happen during
// calculations in the MD2D engine itself are "baked in" using the engine's internal constants and
// units module 'md2d/models/engine/constants/index.js')

// Additionally, since we don't yet offer user-facing methods which do unit conversions (e.g.,
// allowing a property setter to accept an argument containing a value and a unit) there is no
// need for quantitative information in this definition.

define('models/md2d/models/unit-definitions/md2d',[],function() {
  return {
    name: "md2d",
    translated: false,
    units: {

      length: {
        name: "nanometer",
        pluralName: "nanometers",
        symbol: "nm"
      },

      // Internally, we've referred to "Dalton" but amu is probably more common. Dalton is
      // officially more correct but it still seems mostly to be used for protein masses, etc.
      mass: {
        name: "atomic mass unit",
        pluralName: "atomic mass units",
        symbol: "amu"
      },

      time: {
        name: "femtosecond",
        pluralName: "femtoseconds",
        symbol: "fs",
        displayValue: {
          unitsPerBaseUnit: 1e-3,
          pluralName: "picoseconds",
          name: "picosecond",
          symbol: "ps"
        }
      },

      // For unclear reasons, Classic MW scales the damping coefficient of obstacles linearly with
      // the obstacle's mass, so the acceleration due to friction is a constant times the velocity:
      //   a(friction) = -cv
      // For compatibility, MD2D does the same.
      // The units of the constant c (called "obstacle friction") are therefore 1 / time.
      inverseTime: {
        name: "1/femtosecond",
        pluralName: "1/femtoseconds",
        symbol: "1/fs"
      },

      velocity: {
        name: "nanometer per femtosecond",
        pluralName: "nanometers per second",
        symbol: "nm/s"
      },

      acceleration: {
        name: "nanometer per femtosecond squared",
        pluralName: "nanometers per femtosecond squared",
        symbol: "nm/fs²"
      },

      momentum: {
        name: "amu nanometer per femtosecond",
        pluralName: "amu nanometers per femtosecond",
        symbol: "amu⋅nm/fs"
      },

      // Forces haven't typically been exposed to Classic MW users in a quantitative way, and indeed
      // they aren't yet exposed in Next Gen MW, so MD2D doesn't try to translate the
      // (computationally convenient) amu nm/fs² to "user friendly" units. That said, Classic MW
      // could be said to use eV/nm implicitly, since spring constants are in eV/nm².
      force: {
        name: "amu nanometer per femtosecond squared",
        pluralName: "amu nanometers per femtosecond squared",
        symbol: "amu⋅nm/fs²"
      },

      energy: {
        name: "electron volt",
        pluralName: "electron volts",
        symbol: "eV"
      },

      // force / velocity = mass / time; given the composite force unit we use, this is much simpler
      // to write as amu/fs than anything else.
      dampingCoefficient: {
        name: "amu per femtosecond",
        pluralName: "amu per femtoseconds",
        symbol: "amu/fs"
      },

      // aka spring constant (= eV/nm per nm)
      stiffness: {
        name: "electron volt per nanometer squared",
        pluralName: "electron volts per nanometer squared",
        symbol: "eV/nm²"
      },

      // aka torsional spring constant.
      // Dimensions are torque / angle = force (eV/nm) x lever arm (nm) / angle (radians). This is
      // dimensionally equivalent to energy / angle (eV/radians) but the interpretation of force x
      // distance is different for energy than it is for torque.
      rotationalStiffness: {
        name: "electron volt per radian",
        pluralName: "electron volts per radian",
        symbol: "eV/rad"
      },

      charge: {
        name: "elementary charge",
        pluralName: "elementary charges",
        symbol: "e"
      },

      temperature: {
        // Not "degrees Kelvin", just "Kelvin".
        name: "Kelvin",
        // Not "Kelvins", just "Kelvin".
        pluralName: "Kelvin",
        symbol: "K"
      },

      pressure: {
        name: "bar",
        // e.g., "50 bar"
        pluralName: "bar",
        symbol: "bar"
      },

      angle: {
        name: "radian",
        pluralName: "radians",
        symbol: "rad"
      }
    }
  };
});

/*global define: false */

// Definitions of the MKS units used for macroscopic models. We simulate macroscopic models by
// declaring that some value in (microscopic) MD2D units actually represents one macroscopic-scale
// unit of the same type, e.g., we choose 1 nm (a value of 1 in any MD2D property that has unitType
// "length") to represent 1 m.
//
// Such microscopic:macroscopic ratios as 1nm:1m can only be freely chosen for three unit types:
// length, mass, and time. The remaining unit ratios are computed by the UnitsTranslation module.
// See http://lab.dev.concord.org/doc/models/md2d/macroscopic-units/ for an overview of the
// calculations.
//
// In order to compute these ratios, the UnitsTranslation requires two pieces of
// data about each unit:
//
// (1) for mass, length, and time only, the "representationInMD2DUnits" property, which tells
//     how many MD2D units of the same type represent 1 macroscopic unit.
// (2) for each unit type, the "valueInSIUnits" property, which tells the value of that unit in
//     SI units. This is required, for example, if we wanted to have a unit system that represented
//     acceleration in g (multiples of Earth gravity.) We can automatically translate from the MD2D
//     system of units (nm/fs²) to SI/MKS, but without further information relating the value of 1g
//     to units of m/s² we cannot translate MD2D units to g.
//
// Additionally, angle, pressure, and temperature are not included below. That is because angle
// units require no translation, and temperature, while nominally applicable to macroscale models,
// is computed from kinetic energy in a way that is not really applicable to, e.g., a macroscopic
// mass-spring model. Moreover pressure units in Classic MW are somewhat fake, and we don't really
// anticipate trying to compute the pressure exerted by a box of bouncing macroscopic balls.

define('models/md2d/models/unit-definitions/mks',[],function() {
  return {
    name: "mks",
    translated: true,
    units: {

      length: {
        name: "meter",
        pluralName: "meters",
        symbol: "m",
        representationInMD2DUnits: 1,
        valueInSIUnits: 1
      },

      mass: {
        name: "kilogram",
        pluralName: "kilograms",
        symbol: "kg",
        representationInMD2DUnits: 1,
        valueInSIUnits: 1
      },

      time: {
        name: "second",
        pluralName: "seconds",
        symbol: "s",
        representationInMD2DUnits: 1e4,
        valueInSIUnits: 1
      },

      inverseTime: {
        name: "1/second",
        pluralName: "1/seconds",
        symbol: "1/s",
        valueInSIUnits: 1
      },

      velocity: {
        name: "meter per second",
        pluralName: "meters per second",
        symbol: "m/s",
        valueInSIUnits: 1
      },

      acceleration: {
        name: "meter per second squared",
        pluralName: "meters per second squared",
        symbol: "m/s²",
        valueInSIUnits: 1
      },

      force: {
        name: "Newton",
        pluralName: "Newtons",
        symbol: "N",
        valueInSIUnits: 1
      },

      energy: {
        name: "Joule",
        pluralName: "Joules",
        symbol: "J",
        valueInSIUnits: 1
      },

      dampingCoefficient: {
        name: "Newton second per meter",
        pluralName: "Newton seconds per meter",
        symbol: "N⋅s/m",
        valueInSIUnits: 1
      },

      // aka spring constant
      stiffness: {
        name: "Newton per meter",
        pluralName: "Newtons per meter",
        symbol: "N/m",
        valueInSIUnits: 1
      },

      // aka torsional spring constant
      rotationalStiffness: {
        name: "Newton-meter per radian",
        pluralName: "Newton-meters per radian",
        symbol: "N⋅m/rad",
        valueInSIUnits: 1
      },

      charge: {
        name: "Coulomb",
        pluralName: "Coulombs",
        symbol: "C",
        valueInSIUnits: 1
      }
    }
  };
});

/*global define*/

define('models/md2d/models/unit-definitions/index',['require','models/md2d/models/unit-definitions/md2d','models/md2d/models/unit-definitions/mks','underscore'],function(require) {
  var defs = {
        md2d: require('models/md2d/models/unit-definitions/md2d'),
        mks: require('models/md2d/models/unit-definitions/mks')
      },
      _ = require('underscore');

  return {
    get: function(name) {
      var ret;
      if (name === 'md2d') return defs.md2d;

      // For any unit type not in defs[name].units (e.g., temperature does not need to be redefined
      // in MKS), fall back to the MD2D unit definition.
      ret = _.extend({}, defs[name]);
      ret.units = _.extend({}, defs.md2d.units, defs[name].units);
      return ret;
    }
  };
});

/*global define*/
/*jshint eqnull: true*/

define('models/md2d/models/units-translation',['require','underscore','models/md2d/models/engine/constants/index','models/md2d/models/engine/potentials/coulomb'],function(require) {

  var _ = require('underscore'),
      constants = require('models/md2d/models/engine/constants/index'),
      coulomb = require('models/md2d/models/engine/potentials/coulomb'),
      COULOMB_CONSTANT = constants.COULOMB_CONSTANT.as(constants.unit.METERS_PER_FARAD),
      baseUnitTypes = ['length', 'mass', 'time'];

  return function UnitsTranslation(unitsDefinition) {

    var // multiply MD2D-units value by this to get the value in SI units
        siFactor = {},

        // multiply MD2D-units value by this to get the value in translated (macroscopic) units
        factor = {},

        force;

    _.each(baseUnitTypes, function (unitType) {
      var u = unitsDefinition.units[unitType];
      siFactor[unitType] = u.valueInSIUnits / u.representationInMD2DUnits;
    });

    siFactor.inverseTime = 1 / siFactor.time;
    siFactor.velocity = siFactor.length / siFactor.time;
    siFactor.acceleration = siFactor.velocity / siFactor.time;
    siFactor.force = siFactor.mass * siFactor.acceleration;

    // The factor should first convert an MD2D value, which is in *eV*, to amu nm/fs^2:
    siFactor.energy = constants.ratio(constants.unit.MW_ENERGY_UNIT, { per: constants.unit.EV });
    // Then it should convert amu/fs^2 to N and nm to m, yielding Joules:
    siFactor.energy *= siFactor.force * siFactor.length;

    siFactor.dampingCoefficient = siFactor.force / siFactor.velocity;
    // stiffness is eV/nm^2; convert eV -> J and 1/nm^2 -> 1/m^2 (yielding N/m)
    siFactor.stiffness = siFactor.energy / siFactor.length / siFactor.length;
    // rotational stiffness is in eV/rad; convert eV -> N⋅m -- no need to convert radians
    siFactor.rotationalStiffness = siFactor.energy / siFactor.length;

    // Force between charge +1 and -1, 1 distance unit apart, with dielectric constant 1
    force = coulomb.force(1, -1, 1, 1);
    // See disdcussion at http://lab.dev.concord.org/doc/models/md2d/macroscopic-units/
    siFactor.charge = Math.sqrt(force * siFactor.force * siFactor.length * siFactor.length / COULOMB_CONSTANT);

    _.each(_.keys(siFactor), function(unitType) {
      factor[unitType] = siFactor[unitType] / unitsDefinition.units[unitType].valueInSIUnits;
    });

    return {
      translateToModelUnits: function(translatedUnitsValue, unitType) {
        if (factor[unitType] == null) {
          return translatedUnitsValue;
        }
        return translatedUnitsValue / factor[unitType];
      },

      translateFromModelUnits: function(md2dUnitsValue, unitType) {
        if (factor[unitType] == null) {
          return md2dUnitsValue;
        }
        return md2dUnitsValue * factor[unitType];
      }
    };
  };
});


(function() {

  define('cs!models/md2d/models/solvent',['require'],function(require) {
    var Solvent, TYPES;
    TYPES = {
      vacuum: {
        forceType: 0,
        dielectricConstant: 1,
        color: "#eee"
      },
      oil: {
        forceType: -1,
        dielectricConstant: 10,
        color: "#f5f1dd"
      },
      water: {
        forceType: 1,
        dielectricConstant: 80,
        color: "#B8EBF0"
      }
    };
    /*
      Simple class representing a solvent.
    */

    return Solvent = (function() {
      /*
          Constructs a new Solvent.
          @type is expected to be 'oil', 'water' or 'vacuum' string.
      */

      function Solvent(type) {
        var property, propsHash, value;
        this.type = type;
        propsHash = TYPES[this.type];
        if (!(propsHash != null)) {
          throw new Error("Solvent: unknown type. Use 'vacuum', 'oil' or 'water'.");
        }
        for (property in propsHash) {
          value = propsHash[property];
          this[property] = value;
        }
      }

      return Solvent;

    })();
  });

}).call(this);

/*global define */
/*jshint multistr: true */

define('models/md2d/views/nucleotide-paths',[],function () {
  return {
    "outline": {
      "A": "M20.7,0.4l-0.075-0.075C20.672,0.273,20.73,0.231,20.8,0.2 \
            c0.122-0.049,0.247-0.058,0.375-0.025c0.031,0.002,0.064,0.011,0.1,0.025c0.151,0.054,0.26,0.154,0.325,0.3l6.35,14.15 \
            c0.026,0.065,0.043,0.132,0.05,0.2c0.092,0.104,0.142,0.229,0.15,0.375c0.01,0.163-0.041,0.304-0.15,0.425l-9.025,9.85 \
            c-1.65,1.667-3.292,3.55-4.925,5.65L0.2,15.65c-0.11-0.121-0.16-0.262-0.15-0.425c0.008-0.146,0.058-0.271,0.15-0.375 \
            c0.005-0.068,0.021-0.134,0.05-0.2L6.6,0.5c0.074-0.146,0.19-0.246,0.35-0.3c0.151-0.064,0.301-0.064,0.45,0 \
            c0.038,0.019,0.071,0.043,0.1,0.075C7.593,0.339,7.66,0.431,7.7,0.55c0.047,0.112-0.012,0.154-0.175,0.125 \
            C7.461,0.656,7.386,0.63,7.3,0.6L1.65,15.125l12.4,13.625l12.6-13.725L20.7,0.4z",
      "C": "M12.45,0c2.167,0.5,4.066,1.95,5.7,4.35 \
            c2.033,2.967,3.05,6.533,3.05,10.7c0,3.8-0.867,7.117-2.6,9.95c-0.067,0.133-0.167,0.217-0.3,0.25c-0.133,0.067-0.267,0.067-0.4,0 \
            c-0.133-0.033-0.233-0.117-0.3-0.25c-0.133-0.233-0.283-0.45-0.45-0.65V24.3c-1.8-2.667-3.983-4-6.55-4 \
            c-2.567,0.034-4.767,1.383-6.6,4.05c-0.167,0.2-0.3,0.417-0.4,0.65c-0.1,0.133-0.217,0.217-0.35,0.25 \
            c-0.133,0.067-0.267,0.067-0.4,0c-0.133-0.033-0.233-0.117-0.3-0.25C0.85,22.167,0,18.85,0,15.05c0-4.167,1-7.733,3-10.7 \
            C4.633,1.95,6.55,0.5,8.75,0H12.45z M4,5.05V5C3.991,5.016,3.982,5.033,3.975,5.05C2.289,7.935,1.431,11.126,1.4,14.625 \
            c-0.03,3.519,0.536,6.477,1.7,8.875c2.066-3.39,4.566-5.064,7.5-5.025c2.933,0.04,5.433,1.731,7.5,5.075 \
            c1.088-2.518,1.605-5.559,1.55-9.125c-0.055-3.565-0.888-6.69-2.5-9.375V5c-1.8-2.397-3.983-3.589-6.55-3.575 \
            C8.033,1.439,5.833,2.647,4,5.05z",
      "G": "M8.75,0h3.675c2.191,0.504,4.1,1.963,5.725,4.375V4.35 \
            c2.035,2.963,3.052,6.53,3.05,10.7c0.001,4.17-1.015,7.737-3.05,10.7v-0.025c-2.066,3.065-4.583,4.59-7.55,4.575 \
            c-2.966,0.023-5.5-1.494-7.6-4.55v-0.025C0.999,22.77-0.001,19.211,0,15.05c-0.001-4.161,0.999-7.72,3-10.675V4.35 \
            C4.647,1.953,6.564,0.503,8.75,0z M17.15,25.05c1.754-2.938,2.621-6.354,2.6-10.25c-0.022-3.895-0.889-7.145-2.6-9.75V5.025 \
            c-1.8-2.269-3.984-3.402-6.55-3.4C8.033,1.63,5.833,2.771,4,5.05V5.025c-1.646,2.778-2.496,6.12-2.55,10.025 \
            C1.396,18.955,2.246,22.297,4,25.075V25.05c1.583,2.593,3.667,3.985,6.25,4.175c2.583,0.189,4.883-1.194,6.9-4.15V25.05z",
      "T": "M7.45,0.2H7.9C7.794,0.442,7.677,0.709,7.55,1 \
            C6.388,3.721,4.504,8.396,1.9,15.025l6.9,8.35l1.025-1.35c1.258-1.424,2.55-2.816,3.875-4.175l0.025-0.025 \
            c0.148-0.186,0.314-0.336,0.5-0.45c0.046-0.028,0.096-0.036,0.15-0.025c0.059,0.007,0.109,0.032,0.15,0.075l5.325,5.95l6.95-8.35 \
            C24.252,8.584,22.369,3.909,21.15,1c-0.127-0.284-0.243-0.551-0.35-0.8h0.6h0.575c0.041,0.045,0.075,0.095,0.1,0.15l6.35,14.15 \
            c0.028,0.066,0.053,0.133,0.075,0.2c0.087,0.129,0.137,0.279,0.15,0.45c0.01,0.238-0.065,0.447-0.225,0.625l-8.2,8.95 \
            c-0.102,0.12-0.227,0.204-0.375,0.25c-0.167,0.043-0.334,0.043-0.5,0c-0.147-0.046-0.272-0.129-0.375-0.25l-4.675-5.1l-4.625,5.1 \
            c-0.101,0.121-0.226,0.204-0.375,0.25c-0.167,0.043-0.334,0.043-0.5,0c-0.147-0.046-0.272-0.129-0.375-0.25l-8.15-8.95 \
            c-0.16-0.178-0.235-0.387-0.225-0.625c0.014-0.168,0.063-0.318,0.15-0.45c0.019-0.066,0.044-0.133,0.075-0.2l6.35-14.15V0.325 \
            C6.649,0.28,6.674,0.238,6.7,0.2H7.45z",
      "U": "M20.8,0.2h0.6h0.575c0.041,0.045,0.075,0.095,0.1,0.15 \
            l6.35,14.15c0.028,0.066,0.053,0.133,0.075,0.2c0.087,0.129,0.137,0.279,0.15,0.45c0.01,0.238-0.065,0.447-0.225,0.625l-8.2,8.95 \
            c-0.102,0.12-0.227,0.204-0.375,0.25c-0.167,0.043-0.334,0.043-0.5,0c-0.147-0.046-0.272-0.129-0.375-0.25l-4.675-5.1l-4.625,5.1 \
            c-0.101,0.121-0.226,0.204-0.375,0.25c-0.167,0.043-0.334,0.043-0.5,0c-0.147-0.046-0.272-0.129-0.375-0.25l-8.15-8.95 \
            c-0.16-0.178-0.235-0.387-0.225-0.625c0.014-0.168,0.063-0.318,0.15-0.45c0.019-0.066,0.044-0.133,0.075-0.2l6.35-14.15V0.325 \
            C6.649,0.28,6.674,0.238,6.7,0.2h0.75H7.9C7.794,0.442,7.677,0.709,7.55,1C6.388,3.721,4.504,8.396,1.9,15.025l6.9,8.35 \
            l1.025-1.35c1.258-1.424,2.55-2.816,3.875-4.175l0.025-0.025c0.148-0.186,0.314-0.336,0.5-0.45 \
            c0.046-0.028,0.096-0.036,0.15-0.025c0.059,0.007,0.109,0.032,0.15,0.075l5.325,5.95l6.95-8.35C24.252,8.584,22.369,3.909,21.15,1 \
            C21.023,0.716,20.907,0.449,20.8,0.2z"
    },
    "interior": {
      "A": "M21.175,0.175C21.046,0.143,20.921,0.151,20.8,0.2 \
            c-0.069,0.031-0.128,0.073-0.175,0.125c-0.055,0.054-0.097,0.121-0.125,0.2c-0.063,0.16,0.003,0.118,0.2-0.125l5.95,14.625 \
            L14.05,28.75L1.65,15.125L7.3,0.6c0.086,0.03,0.161,0.056,0.225,0.075C7.688,0.704,7.747,0.662,7.7,0.55 \
            C7.66,0.431,7.593,0.339,7.5,0.275L7.6,0.2c4.8-0.067,7.283-0.1,7.45-0.1l6.1,0.05L21.175,0.175z",
      "C": "M3.975,5.05H4c1.833-2.402,4.033-3.611,6.6-3.625 \
            C13.167,1.411,15.35,2.603,17.15,5v0.05c1.612,2.685,2.445,5.81,2.5,9.375c0.055,3.566-0.461,6.607-1.55,9.125 \
            c-2.067-3.344-4.567-5.035-7.5-5.075c-2.934-0.04-5.434,1.635-7.5,5.025c-1.164-2.398-1.73-5.356-1.7-8.875 \
            C1.431,11.126,2.289,7.935,3.975,5.05z",
      "G": "M17.15,25.05v0.025c-2.017,2.956-4.317,4.339-6.9,4.15 \
            C7.667,29.035,5.583,27.643,4,25.05v0.025c-1.754-2.778-2.604-6.12-2.55-10.025C1.504,11.145,2.354,7.803,4,5.025V5.05 \
            c1.833-2.278,4.033-3.42,6.6-3.425c2.566-0.002,4.75,1.131,6.55,3.4V5.05c1.711,2.605,2.578,5.855,2.6,9.75 \
            C19.771,18.695,18.904,22.112,17.15,25.05z",
      "T": "M21.4,0.2h-0.6c0.107,0.249,0.223,0.516,0.35,0.8 \
            c1.219,2.909,3.103,7.584,5.65,14.025l-6.95,8.35l-5.325-5.95c-0.041-0.042-0.091-0.067-0.15-0.075 \
            c-0.054-0.011-0.104-0.003-0.15,0.025c-0.186,0.114-0.352,0.264-0.5,0.45L13.7,17.85c-1.325,1.358-2.617,2.75-3.875,4.175 \
            L8.8,23.375l-6.9-8.35C4.504,8.396,6.388,3.721,7.55,1C7.677,0.709,7.794,0.442,7.9,0.2H7.45c0.133-0.067,0.333-0.1,0.6-0.1 \
            c0.406,0,1.606-0.008,3.6-0.025l-1.375,0.05C17.28,0.048,20.955,0.056,21.3,0.15C21.334,0.164,21.368,0.181,21.4,0.2z",
      "U": "M7.9,0.2l0.15-0.1c0.406,0,1.606-0.008,3.6-0.025l-1.375,0.05 \
            c6.009-0.066,9.568-0.066,10.675,0L20.8,0.2c0.107,0.249,0.223,0.516,0.35,0.8c1.219,2.909,3.103,7.584,5.65,14.025l-6.95,8.35 \
            l-5.325-5.95c-0.041-0.042-0.091-0.067-0.15-0.075c-0.054-0.011-0.104-0.003-0.15,0.025c-0.186,0.114-0.352,0.264-0.5,0.45 \
            L13.7,17.85c-1.325,1.358-2.617,2.75-3.875,4.175L8.8,23.375l-6.9-8.35C4.504,8.396,6.388,3.721,7.55,1 \
            C7.677,0.709,7.794,0.442,7.9,0.2z"
    },
    "letter": {
      "A": {
        "1": "M12.85,12.55h2.6l-1.3-3.95L12.85,12.55z M13,6.25h2.4l3.55,10.1 \
              h-2.3L16,14.3h-3.7l-0.7,2.05H9.4L13,6.25z",
        "2": "M18.7,5.95l-3.55,10.1h-2.4l-3.6-10.1h2.2L12.05,8h3.7l0.65-2.05 \
              H18.7z M12.6,9.75l1.3,3.95l1.3-3.95H12.6z"
      },
      "C": {
        "1": "M10.45,6.4c1.667,0,2.883,0.55,3.65,1.65 \
              c0.433,0.633,0.667,1.267,0.7,1.9h-2.1c-0.133-0.5-0.3-0.867-0.5-1.1c-0.4-0.434-0.967-0.65-1.7-0.65S9.183,8.5,8.75,9.1 \
              C8.317,9.733,8.1,10.617,8.1,11.75c0,1.1,0.217,1.933,0.65,2.5c0.467,0.567,1.05,0.85,1.75,0.85c0.733,0,1.283-0.233,1.65-0.7 \
              c0.233-0.267,0.417-0.667,0.55-1.2h2.05c-0.167,1.1-0.617,2-1.35,2.7c-0.733,0.7-1.683,1.05-2.85,1.05 \
              c-1.433,0-2.566-0.467-3.4-1.4c-0.8-0.933-1.2-2.216-1.2-3.85c0-1.767,0.466-3.117,1.4-4.05C8.15,6.817,9.183,6.4,10.45,6.4z",
        "2": "M15,12.35c-0.033,0.633-0.267,1.267-0.7,1.9 \
              c-0.767,1.1-1.983,1.65-3.65,1.65c-1.267,0-2.3-0.417-3.1-1.25c-0.933-0.933-1.4-2.283-1.4-4.05c0-1.633,0.4-2.917,1.2-3.85 \
              c0.833-0.933,1.967-1.4,3.4-1.4c1.167,0,2.117,0.35,2.85,1.05c0.733,0.7,1.183,1.6,1.35,2.7H12.9 \
              c-0.133-0.533-0.316-0.934-0.55-1.2c-0.367-0.467-0.917-0.7-1.65-0.7c-0.7,0-1.284,0.283-1.75,0.85 \
              c-0.434,0.567-0.65,1.4-0.65,2.5c0,1.133,0.217,2.017,0.65,2.65c0.433,0.6,1.017,0.9,1.75,0.9c0.733,0,1.3-0.217,1.7-0.65 \
              c0.2-0.233,0.367-0.6,0.5-1.1H15z"
      },
      "G": {
        "1": "M14.8,10.8h-2.05c-0.167-0.667-0.567-1.133-1.2-1.4 \
              c-0.333-0.167-0.716-0.25-1.15-0.25c-0.8,0-1.467,0.3-2,0.9c-0.5,0.633-0.75,1.567-0.75,2.8s0.283,2.1,0.85,2.6 \
              c0.567,0.533,1.2,0.8,1.9,0.8c0.7,0,1.283-0.217,1.75-0.65c0.434-0.4,0.717-0.934,0.85-1.6h-2.35v-1.65h4.2v5.4h-1.4l-0.2-1.25 \
              c-0.4,0.467-0.767,0.8-1.1,1C11.583,17.833,10.9,18,10.1,18c-1.333,0-2.434-0.467-3.3-1.4c-0.867-0.9-1.3-2.167-1.3-3.8 \
              s0.45-2.933,1.35-3.9c0.867-1,2.05-1.5,3.55-1.5c1.267,0,2.3,0.333,3.1,1C14.267,9.033,14.7,9.833,14.8,10.8z",
        "2": "M10.65,9.65c-0.7,0-1.333,0.267-1.9,0.8 \
              c-0.567,0.5-0.85,1.367-0.85,2.6c0,1.233,0.25,2.167,0.75,2.8c0.533,0.6,1.2,0.9,2,0.9c0.434,0,0.817-0.083,1.15-0.25 \
              c0.633-0.267,1.034-0.733,1.2-1.4h2.05c-0.1,0.967-0.533,1.767-1.3,2.4c-0.8,0.667-1.833,1-3.1,1c-1.5,0-2.683-0.5-3.55-1.5 \
              c-0.9-0.967-1.35-2.267-1.35-3.9s0.433-2.9,1.3-3.8c0.867-0.933,1.967-1.4,3.3-1.4c0.8,0,1.483,0.167,2.05,0.5 \
              c0.333,0.2,0.7,0.533,1.1,1l0.2-1.25h1.4v5.4h-4.2V11.9h2.35c-0.133-0.667-0.417-1.2-0.85-1.6C11.933,9.867,11.35,9.65,10.65,9.65z"
      },
      "T": {
        "1": "M10.3,5 L18.45,5 L18.45,6.8 L15.4,6.8 L15.4,15.1 L13.3,15.1 \
              L13.3,6.8 L10.3,6.8z",
        "2": "M10.2,14.65 L10.2,12.85 L13.2,12.85 L13.2,4.55 L15.3,4.55 \
              L15.3,12.85 L18.35,12.85 L18.35,14.65z"
      },
      "U": {
        "1": "M10.15,10.8V4.6h2.15v6.2c0,0.7,0.083,1.217,0.25,1.55 \
              c0.267,0.566,0.817,0.85,1.65,0.85c0.867,0,1.417-0.284,1.65-0.85c0.167-0.333,0.25-0.85,0.25-1.55V4.6h2.15v6.2 \
              c0,1.067-0.167,1.9-0.5,2.5c-0.633,1.1-1.817,1.65-3.55,1.65c-1.733,0-2.917-0.55-3.55-1.65C10.317,12.7,10.15,11.867,10.15,10.8z",
        "2": "M12.45,8.4v6.2H10.3V8.4c0-1.066,0.167-1.9,0.5-2.5 \
              c0.633-1.1,1.817-1.65,3.55-1.65S17.267,4.8,17.9,5.9c0.333,0.6,0.5,1.434,0.5,2.5v6.2h-2.15V8.4c0-0.7-0.083-1.217-0.25-1.55 \
              C15.767,6.283,15.217,6,14.35,6c-0.833,0-1.383,0.283-1.65,0.85C12.533,7.183,12.45,7.7,12.45,8.4z"
      }
    }
  };
});

/*global define, d3 */

define('models/md2d/views/nucleotides',['require','models/md2d/views/nucleotide-paths','common/resources-url'],function (require) {
  var nucleotidePaths = require('models/md2d/views/nucleotide-paths'),
      resourcesUrl = require('common/resources-url'),

      SCALE = 0.007,
      W = {
        "BACKB": 52,
        "A": 28.151,
        "C": 21.2,
        "G": 21.2,
        "T": 28.651,
        "U": 28.651,
        "A_GLOW": 44.125,
        "C_GLOW": 37.2,
        "G_GLOW": 36.2,
        "T_GLOW": 45.566
      },
      H = {
        "BACKB": 14,
        "A": 31.15,
        "C": 25.3,
        "G": 30.3,
        "T": 25.007,
        "U": 25.007,
        "A_GLOW": 44.55,
        "C_GLOW": 41.417,
        "G_GLOW": 45.3,
        "T_GLOW": 40.65
      };

  (function () {
    var name;
    for (name in W) {
      if (W.hasOwnProperty(name)) {
        W[name] *= SCALE;
      }
    }
    for (name in H) {
      if (H.hasOwnProperty(name)) {
        H[name] *= SCALE;
      }
    }
  }());

  function nucleotides() {
    var m2px = null,
        sequence = "",
        direction = 1,
        bonds = 1,
        backbone = "DNA", // if enabled, "RNA" or "DNA" is expected.
        stopCodonsHash = null,
        randomEnter = true,
        glow = false,
        enterExitOnly = false,

        xShift = 0,
        yShift = 0;

    function shift(enabled) {
      var t, r;
      if (enabled) {
        // While adding a new mRNA segment, choose a random starting point along a
        // circle with a certain radius that extends beyond the top DNA strand.
        // Use parametric circle equation: x = r cos(t), y = r sin(t)
        // Limit range of the "t" parameter to: [0.25 * PI, 0.75 * PI) and [1.25 * PI, 1.75 * PI),
        // so new mRNA segments will come only from the top or bottom side of the container.
        t = Math.random() >= 0.5 ? Math.PI * 0.25 : Math.PI * 1.25;
        t += Math.random() * Math.PI * 0.5;
        r = nucleotides.HEIGHT * 6;
        xShift = r * Math.cos(t);
        yShift = r * Math.sin(t);
      } else {
        xShift = yShift = 0;
      }
    }

    function translate(d) {
      return "translate(" + m2px(xShift + nucleotides.WIDTH * (d.idx)) + " " + m2px(yShift) + ")";
    }

    function nucleo(g) {
      g.each(function(d, i) {
        var g = d3.select(this),

            yOffset = backbone ? 0.9 * H.BACKB : 0,
            yStart = m2px(yOffset + 0.5 * H.A),
            yEnd = m2px(yOffset + H.A * 0.97),

            seq = typeof sequence === "function" ? sequence(d, i) : sequence,

            nucleo, nucleoEnter, nucleoExit, nucleoGEnter, backboneEnter,
            nucleoShape, nucleoSVG, nucleoSVGUpdate, nucleoTrans, targetScale;

        if (typeof seq === "string") {
          // seq is a string, generate data array. Change it to array of objects.
          // e.g. "AG" will be change to [{idx: 0, type: "A"}, {idx: 1, type: "G"}].
          seq = seq.split("");
          seq.forEach(function(val, i) {
            seq[i] = {id: i, idx: i, type: val};
          });
        }

        // Join data by ID.
        nucleo = g.selectAll(".nucleotide").data(seq, function (d) { return d.id; });
        nucleoEnter = nucleo.enter();
        nucleoExit = nucleo.exit();

        // Enter.
        // Random initial positions of the new mRNAs.
        shift(randomEnter);
        nucleoEnter = nucleoEnter.append("g").attr({
          "transform": translate
        }).style({
          "opacity": randomEnter ? 0 : 1
        });
        // Additional container for scaling.
        nucleoGEnter = nucleoEnter.append("g").attr({
          "class": "scale",
          "transform": "scale(1, " + (direction  === 1 ? 1 : -1) + ")",
        });
        // Bonds.
        nucleoGEnter.append("path").attr("class", "bonds")
          .style({
            "stroke-width": m2px(0.01),
            "stroke": "#fff"
          });
        // Main shape.
        nucleoShape = nucleoGEnter.append("g")
          .classed("nucleo-shape", true)
          .classed("clickable-nucleo", function (d) {
            return d.region === "c" && glow;
          }).on("click", function () {
            // Mobile Safari will only produce mouse events when the user taps
            // on a clickable element, like a link. You can make an element
            // clickable by adding an onClick event handler to it, even if that
            // handler does nothing. It's necessary, as nucleotides should be
            // clickable, e.g. to show context menu.
          });
        // Optional glow image.
        if (glow) {
          nucleoShape.append("image").attr({
            "class": "glow",
            "y": m2px(yOffset - 0.17 * W.G_GLOW),  // move glow closer to the backbone
            "preserveAspectRatio": "none"
          });
        }
        // Parts of nucleotide shape (outline, interior, letter).
        nucleoSVG = nucleoShape.append("svg").attr({
          "y": m2px(yOffset),
          "preserveAspectRatio": "none",
        });
        nucleoSVG.append("path").attr({
          "class": "outline",
          "fill-rule": "evenodd",
          "clip-rule": "evenodd"
        });
        nucleoSVG.append("path").attr({
          "class": "interior",
          "fill-rule": "evenodd",
          "clip-rule": "evenodd"
        });
        nucleoSVG.append("path").attr({
          "class": "letter",
          "fill-rule": "evenodd",
          "clip-rule": "evenodd",
          "d": function (d) { return nucleotidePaths.letter[d.type][direction]; }
        });
        // Optional backbone.
        if (backbone) {
          backboneEnter = nucleoGEnter.append("image").attr({
            "class": "backbone",
            "x": 0,
            "y": 0,
            "width": m2px(W.BACKB),
            "height": m2px(H.BACKB),
            "preserveAspectRatio": "none",
            "xlink:href": resourcesUrl("dna/Backbone_" + backbone + ".svg")
          });
        }

        // Update.
        if (enterExitOnly) {
          // Special mode when we update ONLY nucleotides from enter and exit
          // subselections. It's useful to add new nucleotides while other
          // are being modified by transition at the same time, so it won't
          // be affected.
          nucleo = nucleoEnter;
        }

        // Update without transition.
        nucleo.attr("class", function(d) {
          var regionClass = "";
          switch(d.region) {
            case "c": regionClass = "coding-region"; break;
            case "j": regionClass = "junk-region"; break;
            case "p": regionClass = "promoter-region"; break;
            case "t": regionClass = "terminator-region"; break;
          }
          return "nucleotide " + regionClass;
        });
        nucleo.select(".bonds").attr("d", function (d) {
          if (d.type === "C" || d.type === "G") {
            return "M" + m2px(SCALE * 20) + " " + yStart + " L " + m2px(SCALE * 20) + " " + yEnd +
                   "M" + m2px(SCALE * 26) + " " + yStart + " L " + m2px(SCALE * 26) + " " + yEnd +
                   "M" + m2px(SCALE * 32) + " " + yStart + " L " + m2px(SCALE * 32) + " " + yEnd;
          } else {
            return "M" + m2px(SCALE * 22) + " " + yStart + " L " + m2px(SCALE * 22) + " " + yEnd +
                   "M" + m2px(SCALE * 30) + " " + yStart + " L " + m2px(SCALE * 30) + " " + yEnd;
          }
        });
        nucleo.select(".glow").attr({
          "x": function (d) { return m2px(W.BACKB) / 2 - m2px(W[d.type + "_GLOW"]) / 2; },
          "width": function (d) { return m2px(W[d.type + "_GLOW"]); },
          "height": function (d) { return m2px(H[d.type + "_GLOW"]); },
          "xlink:href": function (d) { return resourcesUrl("dna/NucleotideGlow_" + d.type + ".svg"); }
        });
        nucleoSVGUpdate = nucleo.select(".nucleo-shape > svg");
        nucleoSVGUpdate.attr({
          "class": function (d) {
            var className = "type-" + d.type;
            if (stopCodonsHash && stopCodonsHash[d.idx]) {
              className += " stop-codon";
            }
            return className;
          },
          "viewBox": function (d) { return "0 0 " + (W[d.type] / SCALE) + " " + (H[d.type] / SCALE); },
          "x": function (d) { return m2px(W.BACKB) / 2 - m2px(W[d.type]) / 2; },
          "width": function (d) { return m2px(W[d.type]); },
          "height": function (d) { return m2px(H[d.type]); }
        });
        nucleoSVGUpdate.select("path.interior").attr("d", function (d) {
          return nucleotidePaths.interior[d.type];
        });
        nucleoSVGUpdate.select("path.outline").attr("d", function (d) {
          return nucleotidePaths.outline[d.type];
        });

        // Update with transition.
        shift(false);
        nucleoTrans = d3.transition(nucleo)
          .attr("transform", translate)
          .style("opacity", 1);

        // Animate also bonds opacity.
        nucleoTrans.select(".bonds").style("opacity", bonds);

        // Duck test whether nucleoTrans is really translation. See D3 API
        // Reference - d3.transition(selection) returns transition only when
        // called in the context of other transition. Otherwise it returns
        // selection.
        if (nucleoTrans.attrTween) {
          // Scale. We can't simply use .attr, as rotation is used (to make
          // scale change fancier?). attrTween enforces simple change from
          // scale(1,1) to scale(1,-1) without using rotation.
          targetScale  = "scale(1, " + (direction  === 1 ? 1 : -1) + ")";
          nucleoTrans.select("g.scale").attrTween("transform", function(d, i, a) {
            return d3.interpolateString(a, targetScale);
          });
          // Letters. Default d3 interpolator creates some
          // results which can't be parsed. Use custom interpolator,
          // which changes letters in the middle of transition.
          nucleoTrans.select("path.letter").attrTween("d", function (d, i, a) {
            return function(t) {
              return t < 0.5 ? a : nucleotidePaths.letter[d.type][direction];
            };
          });
        } else {
          // The same operations, but without using transition.
          nucleo.select("g.scale").attr("transform", "scale(1, " + (direction  === 1 ? 1 : -1) + ")");
          nucleo.select("path.letter")
            .attr("d", function (d) { return nucleotidePaths.letter[d.type][direction]; });
        }

        // Exit.
        shift(true);
        d3.transition(nucleoExit)
          .attr("transform", translate)
          .style("opacity", 0)
          .remove();
      });
    }

    nucleo.sequence = function (s) {
      if (!arguments.length) return sequence;
      sequence = s;
      return nucleo;
    };

    nucleo.model2px = function (m) {
      if (!arguments.length) return m2px;
      m2px = m;
      return nucleo;
    };

    nucleo.direction = function (d) {
      if (!arguments.length) return direction;
      direction = d;
      return nucleo;
    };

    nucleo.bonds = function (b) {
      if (!arguments.length) return bonds;
      bonds = b;
      return nucleo;
    };

    nucleo.randomEnter = function (r) {
      if (!arguments.length) return randomEnter;
      randomEnter = r;
      return nucleo;
    };

    /**
     * Enables or disables nucleotide glowing on hover.
     * @param  {boolean} g
     */
    nucleo.glow = function (g) {
      if (!arguments.length) return glow;
      glow = g;
      return nucleo;
    };

    /**
     * @param  {String} b "DNA" or "RNA".
     */
    nucleo.backbone = function (b) {
      if (!arguments.length) return backbone;
      backbone = b;
      return nucleo;
    };

    nucleo.stopCodonsHash = function (s) {
      if (!arguments.length) return stopCodonsHash;
      stopCodonsHash = s;
      return nucleo;
    };

    /**
     * Special mode for quick update of rendered nucleotides number.
     * When this option is set to true, only new nucleotides will be
     * added and other possibly removed. None of existing
     * nucleotides will be updated. It's useful to add new nucleotides
     * while there is an ongoing transition on existing nucleoties.
     * @param  {boolean} ee
     */
    nucleo.enterExitOnly = function (ee) {
      if (!arguments.length) return enterExitOnly;
      enterExitOnly = ee;
      return nucleo;
    };

    return nucleo;
  }

  // Width of the nucleotide is width of the DNA backbone.
  // * 0.92 to ensure that DNA backbone doesn't contain any visual discontinuities.
  // There are two bugs connected with it. First is in Chrome, where preserveAspectRatio
  // is ignored for images, the second one is in Safari, which has problems with correct
  // width of the images. Please see:
  // https://www.pivotaltracker.com/story/show/48453261
  nucleotides.WIDTH  = W.BACKB * 0.92;
  // Height of the nucleotide is height of the DNA backbone + A nucleotide (tallest one).
  // * 0.96 because it simply... looks better. This value is used to determine distance
  // between two strands of DNA and this multiplier causes that they are closer to each other.
  nucleotides.HEIGHT = (H.BACKB * 0.9 + H.A) * 0.96;

  return nucleotides;
});

/*global d3, define */

define('models/md2d/models/engine/genetic-engine',['require','cs!models/md2d/models/aminoacids-helper','common/alert','models/md2d/views/nucleotides'],function (require) {

  var aminoacidsHelper = require('cs!models/md2d/models/aminoacids-helper'),
      alert            = require('common/alert'),
      NUCLEO_WIDTH     = require('models/md2d/views/nucleotides').WIDTH,

      STATES = [
        "undefined",
        "intro-cells",
        "intro-zoom1",
        "intro-zoom2",
        "intro-zoom3",
        "intro-polymerase",
        "dna",
        "transcription",
        "transcription-end",
        "after-transcription",
        "before-translation",
        "translation",
        "translation-end"
      ],
      STATE_INDEX = {},

      NUCLEO_LAST_ID = 0,

      PROMOTER_SEQ   = "TGACCTCTCCGCGCCATCTATAAACCGAAGCGCTAGCTACA",
      TERMINATOR_SEQ = "ACCACAGGCCGCCAGTTCCGCTGGCGGCATTTT",
      PROMOTER_COMP_SEQ   = complementarySequence(PROMOTER_SEQ),
      TERMINATOR_COMP_SEQ = complementarySequence(TERMINATOR_SEQ),
      JUNK_SEQ,
      JUNK_COMP_SEQ,

      DEF_EVENT = "change";

  function complementarySequence(DNA) {
    // A-T (A-U)
    // G-C
    // T-A (U-A)
    // C-G

    // Use lower case during conversion to
    // avoid situation when you change A->T,
    // and later T->A again.
    return DNA
            .replace(/A/g, "t")
            .replace(/G/g, "c")
            .replace(/T/g, "a")
            .replace(/C/g, "g")
            .toUpperCase();
  }
  // Generates junk DNA sequence.
  function junkSequence(len) {
    var letters = ["A", "G", "T", "C"],
        lettersLen = letters.length,
        seq = "", i;
    for (i = 0; i < len; i++) {
      seq += letters[Math.floor(Math.random() * lettersLen)];
    }
    return seq;
  }

  function getNucleoID() {
    return NUCLEO_LAST_ID++;
  }

  (function () {
    var i, len;
    for (i = 0, len = STATES.length; i < len; i++) {
      STATE_INDEX[STATES[i]] = i;
    }
    JUNK_SEQ = junkSequence(50);
    JUNK_COMP_SEQ = complementarySequence(JUNK_SEQ);
  }());

  return function GeneticProperties(model) {
    var api,
        // Do not change this variable manually. It's changed in set() private
        // function. It decides what type of event should be dispatched when
        // DNA or DNAState is updated.
        eventMode = DEF_EVENT,
        // List of transitions, which are currently ongoing (index 0)
        // or scheduled (index > 0).
        ongoingTransitions = [],
        // DNA complementary sequence.
        DNAComp = "",
        // Complete mRNA based on current DNA. Useful for codon() method,
        // which needs to know the whole sequence in advance.
        mRNA = "",
        // Stop codons.
        stopCodonsHash,
        // Index of the first stop codon.
        lastTranslationStep,

        dispatch = d3.dispatch("change", "transition"),

        calculatemRNA = function () {
          var newCode = mRNACode(0),
              mRNA = "";
          while(newCode) {
            mRNA += newCode;
            newCode = mRNACode(mRNA.length);
          }
          return mRNA;
        },

        calculateStopCodonsHash = function () {
          var codon, i, len;

          stopCodonsHash = {};
          lastTranslationStep = null;
          for (i = 0, len = mRNA.length; i < len; i += 3) {
            codon = mRNA.substr(i, 3);
            if (aminoacidsHelper.codonToAbbr(codon) === "STOP") {
              if (lastTranslationStep === null) {
                lastTranslationStep = i / 3;
              }
              // Note that codonToAbbr returns "STOP" also when codon length is
              // smaller than 3. In this case, we want to mark only codons which
              // are a "real" STOP codons, so check their length.
              if (codon.length === 3) {
                stopCodonsHash[i] = stopCodonsHash[i + 1] = stopCodonsHash[i + 2] = true;
              }
            }
          }
          if (lastTranslationStep === null) {
            // No stop codon found.
            lastTranslationStep = Math.floor(mRNA.length / 3);
          }
        },

        mRNACode = function (index) {
          if (index >= DNAComp.length) {
            // No more DNA to transcribe, return null.
            return null;
          }
          switch (DNAComp[index]) {
            case "A": return "U";
            case "G": return "C";
            case "T": return "A";
            case "C": return "G";
          }
        },

        generateMRNAViewModel = function (array, sequence) {
          var i, len, nucleo;
          // Set size of the existing array to the size of new DNA sequence.
          array.length = sequence.length;
          for (i = 0, len = sequence.length; i < len; i++) {
            nucleo = array[i] || {}; // reuse existing objects.
            nucleo.idx = i;
            // Note that only nucleotides whose type doesn't match sequence
            // will receive new ID. It lets you to update this array manually,
            // so the ID as prevented in case of need (e.g. single insertion
            // or deletion during mutation).
            if (nucleo.type !== sequence[i]) {
              nucleo.type = sequence[i];
              nucleo.id   = getNucleoID();
              // This block will be also executed when we insert objects for
              // the first time so update the array[i] reference.
              array[i] = nucleo;
            }
          }
          return array;
        },

        generateDNAViewModel = function (array, DNA, comp) {
          var sequence = !comp ? JUNK_SEQ + PROMOTER_SEQ + DNA + TERMINATOR_SEQ + JUNK_SEQ :
                         JUNK_COMP_SEQ + PROMOTER_COMP_SEQ + DNA + TERMINATOR_COMP_SEQ + JUNK_COMP_SEQ,
              junkBound = JUNK_SEQ.length,
              promoterBound = junkBound + PROMOTER_SEQ.length,
              codingBound = promoterBound + DNA.length,
              terminatorBound = codingBound + TERMINATOR_SEQ.length,
              i, len, nucleo;

          // Set size of the existing array to the size of new DNA sequence.
          array.length = sequence.length;
          for (i = 0, len = sequence.length; i < len; i++) {
            nucleo = array[i] || {}; // reuse existing objects.
            nucleo.idx = i;
            if (i < junkBound)            nucleo.region = "j";
            else if (i < promoterBound)   nucleo.region = "p";
            else if (i < codingBound)     nucleo.region = "c";
            else if (i < terminatorBound) nucleo.region = "t";
            else                          nucleo.region = "j";
            // Note that only nucleotides whose type doesn't match sequence
            // will receive new ID. It lets you to update this array manually,
            // so the ID as prevented in case of need (e.g. single insertion
            // or deletion during mutation).
            if (nucleo.type !== sequence[i]) {
              nucleo.type = sequence[i];
              nucleo.id   = getNucleoID();
              // This block will be also executed when we insert objects for
              // the first time so update the array[i] reference.
              array[i] = nucleo;
            }
          }
          return array;
        },

        updateGeneticProperties = function () {
          var DNA = model.get("DNA");

          generateDNAViewModel(api.viewModel.DNA, DNA, false);

          DNAComp = complementarySequence(DNA);
          generateDNAViewModel(api.viewModel.DNAComp, DNAComp, true);

          mRNA = calculatemRNA();
          calculateStopCodonsHash();
          // mRNA view array is also based on the current state.
          if (api.stateBefore("transcription:0")) {
            generateMRNAViewModel(api.viewModel.mRNA, "");
          } else if (api.state().name === "transcription") {
            generateMRNAViewModel(api.viewModel.mRNA, mRNA.substr(0, api.state().step));
          } else if (api.stateAfter("transcription")) {
            // So, the first state which triggers it is "transcription-end".
            generateMRNAViewModel(api.viewModel.mRNA, mRNA);
          }

          if (eventMode !== "transition") {
            // While jumping between states, ensure that user can see a valid
            // number of amino acids.
            if (api.stateBefore("translation:1")) {
              removeAminoAcids();
            } else if (api.stateEqual("translation-end")) {
              generateFinalProtein();
            }
          }
        },

        removeAminoAcids = function () {
          var aaCount;

          aaCount = model.getNumberOfAtoms();
          if (aaCount > 0) {
            model.startBatch();
            while(aaCount > 0) {
              model.removeAtom(aaCount - 1);
              aaCount--;
            }
            model.endBatch();
          }
          model.stop();
        },

        generateFinalProtein = function () {
          var aaSequenece = [],
              i = 0,
              abbr = aminoacidsHelper.codonToAbbr(api.codon(0));

          while(abbr !== "STOP") {
            aaSequenece.push(abbr);
            abbr = aminoacidsHelper.codonToAbbr(api.codon(++i));
          }
          api.generateProtein(aaSequenece, undefined, 2.3, 0.3);
          api.centerProtein();
          model.start();
        },

        nextState = function (state) {
          var name = state.name,
              next, abbr;

          if (name === "transcription") {
            if (state.step < model.get("DNA").length - 1) {
              return "transcription:" + (state.step + 1);
            } else {
              return "transcription-end";
            }
          } else if (name === "translation") {
            abbr = aminoacidsHelper.codonToAbbr(api.codon(state.step));
            if (abbr !== "STOP") {
              return "translation:" + (state.step + 1);
            } else {
              return "translation-end";
            }
          } else {
            // "Typical" state.
            next = STATES[STATE_INDEX[state.name] + 1];
            if (next === "transcription" || next === "translation") {
              next += ":0";
            }
            return next;
          }
        },

        prevState = function (state) {
          var name = state.name,
              step = state.step;

          if (name === "transcription" && step > 0) {
            return "transcription:" + (step - 1);
          } else if (name === "transcription-end") {
            return "transcription:" + (model.get("DNA").length - 1);
          } else if (name === "translation-end" || (name === "translation" && step > 0)) {
            // Note that we always return state translation:0,
            // as jumping between translation steps is not allowed.
            return "translation:0";
          } else {
            return STATES[STATE_INDEX[name] - 1];
          }
        },

        // DNA states comparator.
        stateComp = function (stateA, stateB) {
          if (stateA === stateB) {
            return 0;
          }
          stateA = api.parseState(stateA);
          stateB = api.parseState(stateB);
          if (stateA.name === stateB.name) {
            if (isNaN(stateA.step) || isNaN(stateB.step)) {
              // Note that when you compare e.g. "translate"
              // and "translate:5" these steps are considered to be equal.
              return 0;
            }
            return stateA.step < stateB.step ? -1 : 1;
          }
          return STATE_INDEX[stateA.name] < STATE_INDEX[stateB.name] ? -1 : 1;
        },

        transitionToState = function (name) {
          if (typeof name === "undefined") return;
          if (ongoingTransitions.length > 0) {
            // Some transition are in progress, so only enqueue a new state.
            ongoingTransitions.push(name);
          } else {
            // Mark transition as ongoing (by adding it to the list)
            // and do transition.
            ongoingTransitions.push(name);
            doStateTransition(name);
          }
        },

        doStateTransition = function (name) {
          set("DNAState", name, "transition");
        },

        doDNATransition = function (newDNA) {
          set("DNA", newDNA, "transition");
        },

        dispatchChange = function (suppressViewportUpdate) {
          // Cancel transitions when we are going to dispatch "change" event.
          ongoingTransitions.length = 0;
          model.cancelTransitions();

          dispatch.change(suppressViewportUpdate);
        },

        // Use this function if you want to change DNA or DNAState
        // and dispatch event different than "change" (which causes immediate
        // rendering). Options are:
        // - "change",
        // - "transition",
        // - "suppress".
        set = function(name, value, eventType) {
          eventMode = eventType || DEF_EVENT;
          model.properties[name] = value;
          eventMode = DEF_EVENT;
        },

        stateUpdated = function () {
          var state = model.get("DNAState");

          if (eventMode === "suppress") {
            return;
          }

          updateGeneticProperties();

          if (eventMode === "transition") {
            dispatch.transition(state);
          } else {

            if (api.stateAfter("translation:0") && api.stateBefore("translation-end")) {
              // It means that state was set to 'translation:x', where x > 0.
              // Use the last safe state ('translation:0') instead.
              alert("'" + state + "' cannot be set explicitly. " +
                "'translation:0' should be set and then animation to '" +
                state + "' should be triggered.");
              set("DNAState", "translation:0");
              return;
            }

            dispatchChange();
          }
        },

        DNAUpdated = function () {
          if (eventMode === "suppress") {
            return;
          }

          if (api.stateAfter("translation:0") && api.stateBefore("translation-end")) {
            // Reset translation if DNA is changed. This will remove all
            // existing amino acids and notify renderer (via stateUpdated
            // callback).
            set("DNAState", "translation:0");
            return;
          }

          updateGeneticProperties();

          if (eventMode === "transition") {
            dispatch.transition("dna-updated", true);
          } else {
            dispatchChange(true);
          }
        };

    // Public API.
    api = {
      /**
       * Number of nucleotides before coding region (so, in junk and promoter regions).
       * @type {number}
       */
      PRECODING_LEN: JUNK_SEQ.length + PROMOTER_SEQ.length,

      /**
       * Hash of arrays containing nucleotides objects. Each array can be
       * consumed by the view. References to arrays are guaranteed to be
       * untouched during whole life cycle of the GeneticEngine instance.
       * Only arrays' lengths and content can be changed.
       *
       * Each nucleotide is defined by:
       * type   - letter ("A", "T", "U", "G" or "C"),
       * idx    - its position,
       * id     - unique id,
       * coding - true if nucleotide is a part of coding region (not junk, terminator or promoter).
       */
      viewModel: {
        mRNA: [],
        DNA: [],
        DNAComp: []
      },

      /**
       * Registers listener for given event type. Supported events
       * are: "change" and "transition".
       * @param  {string} type
       * @param  {function} listener
       */
      on: function(type, listener) {
        dispatch.on(type, listener);
      },

      /**
       * Performs substitution mutation on DNA coding region.
       * @param  {number} idx            position in DNA.
       * @param  {string} newType        "A", "T", "G" or "C".
       * @param  {boolean} DNAComplement if true, mutation is performed
       *                                 on DNA complementary strand.
       */
      mutate: function(idx, newType, DNAComplement) {
        var DNA = model.get("DNA"),
            pos = idx - api.PRECODING_LEN;

        if (pos < 0 || pos >= DNA.length) {
          throw new Error("Mutation can be performed only on DNA coding region.");
        }

        DNA = DNA.substr(0, pos) +
              (DNAComplement ? complementarySequence(newType) : newType) +
              DNA.substr(pos + 1);
        // Update DNA. This will also call updateGeneticProperties(), so
        // other, related properties will be also updated.
        set("DNA", DNA);
      },

      /**
       * Performs insertion mutation on DNA coding region.
       * @param  {number} idx            position in DNA.
       * @param  {string} type           "A", "T", "G" or "C".
       * @param  {boolean} DNAComplement if true, mutation is performed
       *                                 on DNA complementary strand.
       */
      insert: function(idx, type, DNAComplement) {
        var newDNANucleo = {
              type: DNAComplement ? complementarySequence(type) : type,
              id: getNucleoID(),
              region: "c"
            },
            newDNACompNucleo = {
              type: DNAComplement ? type : complementarySequence(type),
              id: getNucleoID(),
              region: "c"
            },
            newMRNANucleo = {
              type: DNAComplement ? complementarySequence(type) : type,
              id: getNucleoID(),
              region: "c"
            },
            DNA = model.get("DNA"),
            state = api.state(),
            pos = idx - api.PRECODING_LEN;

        if (pos < 0 || pos >= DNA.length) {
          throw new Error("Mutation can be performed only on DNA coding region.");
        }

        // Update view model arrays. It isn't necessary, but as we update them
        // correctly, nucleotides will preserve their IDs and view will know
        // exactly what part of DNA have been changed.
        api.viewModel.DNA.splice(idx, 0, newDNANucleo);
        api.viewModel.DNAComp.splice(idx, 0, newDNACompNucleo);
        api.viewModel.mRNA.splice(pos, 0, newMRNANucleo);

        // Update DNA. This will also call updateGeneticProperties(), so
        // other, related properties will be also updated.
        DNA = DNA.substr(0, pos) + newDNANucleo.type + DNA.substr(pos);

        // Special case for transcription process (and state):
        // If we keep the same DNAState and we insert something
        // before state.step position, it would cause that the last
        // transcribed nucleotide would be removed. Avoid that, as this can be
        // confusing for users.
        if (state.name === "transcription" && pos < state.step) {
          // Note that we can't use nextState(state), as in that case, as
          // state can be changed to transcription-end too fast (as DNA isn't
          // updated yet).
          set("DNAState", state.name + ":" + (state.step + 1), "suppress");
        }
        doDNATransition(DNA);
      },

      /**
       * Performs deletion mutation on DNA coding region.
       * @param  {number} idx position in DNA.
       */
      delete: function(idx) {
        var DNA = model.get("DNA"),
            state = api.state(),
            pos = idx - api.PRECODING_LEN;

        if (pos < 0 || pos >= DNA.length) {
          throw new Error("Mutation can be performed only on DNA coding region.");
        }

        // Update view model arrays. It isn't necessary, but as we update them
        // correctly, nucleotides will preserve their IDs and view will know
        // exactly what part of DNA have been changed.
        api.viewModel.DNA.splice(idx, 1);
        api.viewModel.DNAComp.splice(idx, 1);
        api.viewModel.mRNA.splice(pos, 1);

        // Update DNA. This will also call updateGeneticProperties(), so
        // other, related properties will be also updated.
        DNA = DNA.substr(0, pos) + DNA.substr(pos + 1);

        // Special case for transcription process (and state):
        // If we keep the same DNAState and we delete something
        // before state.step position, it would cause that new transcribed
        // mRNA nucleotide will be added. Avoid that, as this can be
        // confusing for users.
        if (state.name === "transcription" && pos < state.step) {
          set("DNAState", prevState(state), "suppress");
        }
        doDNATransition(DNA);
      },

      /**
       * Triggers transition to the next DNA state.
       *
       * If any transition was ongoing, it's canceled.
       */
      transitionToNextState: function () {
        api.stopTransition();
        if (ongoingTransitions.length === 0) {
          transitionToState(nextState(api.lastState()));
        }
      },

      /**
       * Stops current animation.
       * @return {boolean} true when some transitions are canceled, false otherwise.
       */
      stopTransition: function () {
        if (ongoingTransitions.length > 1) {
          // Cleanup queue of waiting transitions. ongoingTransitions[0] is
          // the current transition, don't remove it.
          ongoingTransitions.length = 1;
        }
      },

      jumpToNextState: function () {
        if (api.stateBefore("translation:0")) {
          set("DNAState", nextState(api.state()));
        } else if (api.stateBefore("translation-end")) {
          set("DNAState", "translation-end");
        }
      },

      jumpToPrevState: function () {
        if (api.stateAfter("intro-cells")) {
          set("DNAState", prevState(api.state()));
        }
      },

      /**
       * Triggers transition to the given DNA state.
       * e.g. transitionTo("transcription-end")
       *
       * @param  {string} stateName name of the state.
       */
      transitionTo: function (stateName) {
        while (api.lastStateBefore(stateName)) {
          transitionToState(nextState(api.lastState()));
        }
      },

      /**
       * Triggers only one step of DNA transcription.
       * This method also accepts optional parameter - expected nucleotide.
       * When it's available, transcription step will be performed only
       * when passed nucleotide code matches nucleotide, which should
       * be actually joined to mRNA in this transcription step. When
       * expected nucleotide code is wrong, this method does nothing.
       *
       * e.g.
       * transcribeStep("A") will perform transcription step only
       * if "A" nucleotide should be added to mRNA in this step.
       *
       * @param  {string} expectedNucleotide code of the expected nucleotide ("U", "C", "A" or "G").
       */
      transcribeStep: function (expectedNucleotide) {
        var state, newCode;

        state = api.state();
        if (state.name === "dna" && typeof expectedNucleotide === "undefined") {
          api.transitionToNextState();
        } else if (state.name === "transcription") {
          newCode = mRNACode(state.step);
          if (expectedNucleotide && expectedNucleotide.toUpperCase() !== newCode) {
            // Expected nucleotide is wrong, so simply do nothing.
            return;
          }
          api.transitionToNextState();
        }
      },

      // Helper methods used mainly by the genetic renderer.

      /**
       * Returns parsed *current* DNA state.
       * e.g.
       * {
       *   name: "translation",
       *   step: 5
       * }
       *
       * @return {Object} current state object (see above).
       */
      state: function () {
        return api.parseState(model.get("DNAState"));
      },

      stateBefore: function (name) {
        return stateComp(model.get("DNAState"), name) === -1;
      },

      stateEqual: function (name) {
        return stateComp(model.get("DNAState"), name) === 0;
      },

      stateAfter: function (name) {
        return stateComp(model.get("DNAState"), name) === 1;
      },

      /**
       * Returns parsed *last* enqueued state.
       * When there is no state enqueued or in progress,
       * it returns simply current state.
       *
       * e.g.
       * {
       *   name: "translation",
       *   step: 5
       * }
       *
       * @return {Object} last enqueued state object (see above).
       */
      lastState: function () {
        var queueLen = ongoingTransitions.length;
        if (queueLen > 0) {
          return api.parseState(ongoingTransitions[queueLen - 1]);
        }
        return api.state();
      },

      lastStateBefore: function (name) {
        var queueLen = ongoingTransitions.length,
            lastStateName = queueLen ? ongoingTransitions[queueLen - 1] : model.get("DNAState");
        return stateComp(lastStateName, name) === -1 ? true : false;
      },

      lastStateAfter: function (name) {
        var queueLen = ongoingTransitions.length,
            lastStateName = queueLen ? ongoingTransitions[queueLen - 1] : model.get("DNAState");
        return stateComp(lastStateName, name) === 1 ? true : false;
      },

      parseState: function (state) {
        // State can contain ":" and info about step.
        // e.g. translation:0, translation:1 etc.
        state = state.split(":");
        return {
          name: state[0],
          step: Number(state[1]) // can be NaN when step is undefined.
        };
      },

      codon: function (index) {
        return mRNA.substr(3 * index, 3);
      },

      codonComplement: function (index) {
        return api.codon(index)
            .replace(/A/g, "u")
            .replace(/G/g, "c")
            .replace(/U/g, "a")
            .replace(/C/g, "g")
            .toUpperCase();
      },

      translationStepStarted: function (codonIdx, x, y, xEnd, yEnd, duration) {
        var abbr = aminoacidsHelper.codonToAbbr(api.codon(codonIdx)),
            elID = aminoacidsHelper.abbrToElement(abbr);

        // Add some entropy to y position to avoid perfectly straight line of
        // amino acids what can affect folding process.
        yEnd += Math.random() * 0.02 - 0.01;
        model.addAtom({x: x, y: y, element: elID, visible: true, pinned: true}, {suppressCheck: true});
        // Transition new amino acid to its final position.
        model.atomTransition().id(codonIdx).duration(duration).prop("x", xEnd);
        model.atomTransition().id(codonIdx).duration(duration).prop("y", yEnd);
        // Ensure that the simulation is started.
        model.start();
      },

      shiftAminoAcids: function (count, xShift, duration) {
        if (count < 1) return;
        var i, x, y;
        // Shift amino acids to the right.
        for (i = 0; i < count; i++) {
          x = model.getAtomProperties(i).x + xShift;
          y = model.getAtomProperties(i).y;
          model.atomTransition().id(i).duration(duration).prop("x", x);
          // This is required to keep Y coordinate constant during this
          // transition, some forces applied by the MD2D engine can
          // change trajectory of the particle.
          model.atomTransition().id(i).duration(duration).prop("y", y);
        }
      },

      centerProtein: function (duration) {
        model.batch(function () {
          var cm = api.proteinCenterOfMass(),
              xDiff = model.properties.viewPortX +
                      model.properties.viewPortWidth / 2 - cm.x,
              yDiff = model.properties.viewPortY +
                      model.properties.viewPortHeight / 2 - cm.y,
              minX = model.properties.minX + 0.1,
              maxX = model.properties.maxX - 0.1,
              minY = model.properties.minY + 0.1,
              maxY = model.properties.maxY - 0.1,
              len  = model.getNumberOfAtoms(),
              i, x, y;

          for (i = 0; i < len; i++) {
            x = model.getAtomProperties(i).x;
            y = model.getAtomProperties(i).y;
            if (x + xDiff > maxX) xDiff = maxX - x;
            if (x + xDiff < minX) xDiff = minX - x;
            if (y + yDiff > maxY) yDiff = maxY - y;
            if (y + yDiff < minY) yDiff = minY - y;
          }
          for (i = 0; i < len; i++) {
            x = model.getAtomProperties(i).x + xDiff;
            y = model.getAtomProperties(i).y + yDiff;
            if (duration) {
              model.atomTransition().id(i).duration(duration).prop("x", x);
              model.atomTransition().id(i).duration(duration).prop("y", y);
            } else {
              model.setAtomProperties(i, {x: x, y: y});
            }
          }
        });
        model.tickInPlace();
      },

      connectAminoAcid: function (codonIdx) {
        if (codonIdx < 1) return;
        var r1 = model.getAtomProperties(codonIdx - 1).radius,
            r2 = model.getAtomProperties(codonIdx).radius,
            // Length of bond is based on the radii of AAs.
            bondLen = (r1 + r2) * 1.25;
        // 10000 is a typical strength for bonds between AAs.
        model.addRadialBond({atom1: codonIdx, atom2: codonIdx - 1, length: bondLen, strength: 10000});
        model.setAtomProperties(codonIdx - 1, {pinned: false});
      },

      translationCompleted: function () {
        var atomsCount = model.getNumberOfAtoms();
        if (atomsCount > 0) {
          // Unpin the last atom. Note that sometimes translation
          // can end without any atom.
          model.setAtomProperties(atomsCount - 1, {pinned: false});
        }
      },

      transitionEnded: function () {
        // Transition has just ended so remove it
        // from transitions list.
        ongoingTransitions.shift();
        if (ongoingTransitions.length > 0) {
          doStateTransition(ongoingTransitions[0]);
        }
      },

      stopCodonsHash: function () {
        return stopCodonsHash;
      },

      lastTranslationStep: function () {
        return lastTranslationStep;
      },

      /**
       * Returns center of mass coridantes of the whole protein.
       * When there are no amino acids, returns null.
       *
       * @return {Object|null} protein's center of mass, e.g. {x: 1, y: 2}
       *                       or null when there are no amino acids.
       */
      proteinCenterOfMass: function () {
        var totalMass = 0,
            xcm = 0,
            ycm = 0,
            len = model.getNumberOfAtoms(),
            atom, i;

        if (len === 0) {
          return null;
        }

        // Note that there is a strong asumption that there are *only* amino
        // acids in the model.
        for (i = 0, len = model.getNumberOfAtoms(); i < len; i++) {
          atom = model.getAtomProperties(i);
          xcm += atom.x * atom.mass;
          ycm += atom.y * atom.mass;
          totalMass += atom.mass;
        }
        xcm /= totalMass;
        ycm /= totalMass;
        return {
          x: xcm,
          y: ycm
        };
      },

      /**
       * Generates a new protein (and removes all existing atoms before).
       *
       * @param  {array} aaSequence      defines expected sequence of amino acids. Pass undefined and provide
       *                                 'expectedLength' if you want to generate a random protein.
       * @param  {Number} expectedLength controls the maximum (and expected) number of amino
       *                                 acids of the resulting protein. Provide this parameter only when 'aaSequence'
       *                                 is undefined. When expected length is too big (due to limited area of the model),
       *                                 the protein will be truncated and its real length returned.
       * @return {Number}                number of created amino acids (<= expectedLength).
       */
      generateProtein: function (aaSequence, expectedLength, paddingTop, paddingBottom) {
        // Process arguments.
        expectedLength = aaSequence ? aaSequence.length : expectedLength;
        paddingTop = paddingTop || 0;
        paddingBottom = paddingBottom || 0;

        var minX = model.properties.minX,
            minY = model.properties.minY + paddingBottom,
            maxX = model.properties.maxX,
            maxY = model.properties.maxY - paddingTop,
            createdAA = 0;

        // First, make sure that model is empty.
        removeAminoAcids();

        model.batch(function () {
              // Options for .addAtom modeler's method.
          var opt = {suppressCheck: true},
              width   = maxX - minX,
              height  = maxY - minY,
              aaCount = aminoacidsHelper.lastElementID - aminoacidsHelper.firstElementID + 1,
              xPos, yPos, xStep, yStep, el, props, radius, prevRadius, bondLen, i,

              // This function controls how X coordinate is updated,
              // using current Y coordinate as input.
              turnHeight = 0.6,
              xStepFunc = function(y) {
                if (y > height - turnHeight || y < turnHeight) {
                  // Close to the boundary increase X step.
                  return 0.1;
                }
                return 0.02 - Math.random() * 0.04;
              },

              // This function controls how Y coordinate is updated,
              // using current Y coordinate and previous result as input.
              changeHeight = 0.3,
              yStepFunc = function(y, prev) {
                if (prev === 0) {
                  // When previously 0 was returned,
                  // now it's time to switch direction of Y step.
                  if (y > 0.5 * maxY) {
                    return -0.1;
                  }
                  return 0.1;
                }
                if (yPos > maxY - changeHeight || yPos < changeHeight) {
                  // Close to the boundary return 0 to make smoother turn.
                  return 0;
                }
                // In a typical situation, just return previous value.
                return prev;
              },

              getRandomAA = function() {
                return Math.floor(aaCount * Math.random()) + aminoacidsHelper.firstElementID;
              };

          // Add the first amino acid. Start from the lower-left corner of
          // model area.
          xPos = minX + 0.1;
          yPos = minY + 0.1;
          xStep  = 0;
          yStep  = 0;
          el     = aaSequence ? aminoacidsHelper.abbrToElement(aaSequence[0]) : getRandomAA();
          radius = model.getElementProperties(el).radius;
          props  = {x: xPos, y: yPos, element: el, visible: true};

          model.addAtom(props, opt);
          createdAA += 1;

          // Add remaining amino acids.
          for (i = 1; i < expectedLength; i++) {
            xPos = props.x;
            yPos = props.y;

            // Update step.
            xStep = xStepFunc(yPos);
            yStep = yStepFunc(yPos, yStep);

            // Update coordinates of new AA.
            xPos += xStep * 1.7;
            yPos += yStep * 1.7;

            if (xPos > width - 0.1) {
              // No space left for new AA.
              return;
            }

            el = aaSequence ? aminoacidsHelper.abbrToElement(aaSequence[i]) : getRandomAA();
            props = {x: xPos, y: yPos, element: el, visible: true};
            model.addAtom(props, opt);
            createdAA += 1;

            // Length of bond is based on the radii of AAs.
            prevRadius = radius;
            radius = model.getElementProperties(el).radius;
            bondLen = (radius + prevRadius) * 1.25;
            // 10000 is a typical strength for bonds between AAs.
            model.addRadialBond({atom1: i, atom2: i - 1, length: bondLen, strength: 10000});
          }
        });
        // We have to use a new batch so atoms array will be updated and we
        // can use getAtomProperties for recently added atoms.
        model.batch(function () {
          // Center protein (X coords only) in the viewport. Make sure
          // that we don't exceed model boundaries.
          var proteinsMaxX   = model.getAtomProperties(createdAA - 1).x,
              proteinsCenter = (proteinsMaxX - minX) / 2,
              viewPortCenter = model.properties.viewPortX + model.properties.viewPortWidth / 2,
              spaceOnRight   = maxX - proteinsMaxX,
              shift = Math.max(0, Math.min(viewPortCenter - proteinsCenter, spaceOnRight)),
              i;

          // Shift all AAs.
          for (i = 0; i < expectedLength; i++) {
            model.setAtomProperties(i, {x: model.getAtomProperties(i).x + shift});
          }
        });

        // Minize energy so the protein will look better.
        model.minimizeEnergy();

        // Return number of created AA.
        return createdAA;
      }
    };

    model.addPropertiesListener(["DNA"], DNAUpdated);
    model.addPropertiesListener(["DNAState"], stateUpdated);
    updateGeneticProperties();
    return api;
  };

});

/*global define, $ */

define('models/md2d/models/performance-optimizer',[],function() {

  var MIN_FPS = 2.5;

  function PerformanceOptimizer(model) {
    /** @private */
    this._model = model;
    /** @private */
    this._initialTimeStep = this._model.get('timeStep');
    /** @private */
    this._maxTimeStep = this._initialTimeStep * 2;
    /** @private */
    this._targetProgressRate = this._initialTimeStep * this._model.get('timeStepsPerTick') * 60; // 60fps
    /**
     * Indicates whether performance optimizer is enabled or not.
     * @type {Boolean}
     */
    this.enabled = false;
  }

  PerformanceOptimizer.prototype._assessPerformance = function() {
    if (!this.enabled || this._model.isStopped()) {
      return;
    }

    var progressRate = this._model.getSimulationProgressRate(),
        fps = this._model.getFPS(),
        timeStep = this._model.get('timeStep'),
        timeStepsPerTick = this._model.get('timeStepsPerTick'),
        currentMaxTimeStep = this._model.get('temperatureControl') ? this._maxTimeStep * 2.5 : this._maxTimeStep;

    if (progressRate < 0.9 * this._targetProgressRate) {
      // Try to increase timeStep and if it's impossible,
      // touch timeStepsPerTick (what probably decrease FPS and
      // animation smoothness).
      if (1.1 * timeStep < currentMaxTimeStep) {
        this._model.set('timeStep', 1.1 * timeStep);
      } else if (fps > MIN_FPS) {
        this._model.set('timeStepsPerTick', Math.round(1.1 * timeStepsPerTick + 0.5));
      }
    } else if (progressRate > 1.1 * this._targetProgressRate) {
      // If simulation is going to fast, decrease timeStepsPerTick
      // what should make animations smoother.
      this._model.set('timeStepsPerTick', Math.round(0.9 * timeStepsPerTick - 0.5));
    }
    setTimeout($.proxy(this._assessPerformance, this), 250);
  };

  PerformanceOptimizer.prototype.enable = function() {
    if (this.enabled) {
      return;
    }
    this._model.start();
    this.enabled = true;
    this._model.set('timeStepsPerTick', 5);
    setTimeout($.proxy(this._assessPerformance, this), 250);
  };

  PerformanceOptimizer.prototype.disable = function() {
    this.enabled = false;
  };

  return PerformanceOptimizer;
});

/*global define, d3 */

define('common/models/property-transition',[],function() {

  /**
   * Abstract class, which defines basic interface for property transition.
   * It allows to smoothly change property over desired time period.
   *
   * Note that this class *can't* be instantiated. It should be used
   * as a base class for other classes, which should implement following
   * interface:
   *  - getObjectProperties(id)
   *  - setObjectProperties(id, props)
   *
   * Note that under the hood D3 ease and interpolate methods are used.
   * Also interface is similar to D3 transitions.
   */
  function PropertyTransition() {
    this.isFinished = false;
    this._duration = 0;
    this._easeFunc = d3.ease("cubic-in-out"); // also default in d3
    this._elapsedTime = 0;
    this._id = null;
    this._propName = null;
    this._endValue = null;
    this._interpolator = null;

    // Check whether required methods are implemented.
    // This class can't be instantiated, only subclasses
    // implementing specified interface:
    if (this.getObjectProperties == null) {
      throw new Error("getObjectProperties method must be implemented by descendant!");
    }
    if (this.setObjectProperties == null) {
      throw new Error("setObjectProperties method must be implemented by descendant!");
    }
  }

  /**
   * Sets ID of processed object. It will be passed to
   * getObjectProperties and setObjectProperties.
   * @param  {*} id
   * @return {PropertyTransition} this (method chaining).
   */
  PropertyTransition.prototype.id = function(id) {
    this._id = id;
    return this;
  };

  /**
   * Sets property name and its final value.
   * @param  {String} propName
   * @param  {*}      endValue
   * @return {PropertyTransition} this (method chaining).
   */
  PropertyTransition.prototype.prop = function(propName, endValue) {
    this._propName = propName;
    this._endValue = endValue;
    return this;
  };

  /**
   * Sets transition duration.
   * @param  {number} duration Transition duration.
   * @return {PropertyTransition} this (method chaining).
   */
  PropertyTransition.prototype.duration = function (duration) {
    this._duration = duration;
    return this;
  };

  /**
   * Sets transition delay.
   * @param  {number} delay Transition delay.
   * @return {PropertyTransition} this (method chaining).
   */
  PropertyTransition.prototype.delay = function (delay) {
    this._elapsedTime = -delay;
    return this;
  };

  /**
   * Sets easing function duration. Note that under the hood
   * d3.ease function is used to generate easing function.
   * Please see:
   * https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease
   *
   * @param  {string} funcName Function name.
   * @return {PropertyTransition} this (method chaining).
   */
  PropertyTransition.prototype.ease = function (funcName) {
    this._easeFunc = d3.ease(funcName);
    return this;
  };

  /**
   * Processes the transition. This should be used by the model
   * implementing transitions support. When transition is finished,
   * isFinished field will be set to true.
   * @param  {number} elapsedTime elapsed time, units have to be
   *                              consistent with duration time.
   */
  PropertyTransition.prototype.process = function (elapsedTime) {
    if (this.isFinished || this._incompleteSpec()) {
      return;
    }
    var t, props;

    this._elapsedTime += elapsedTime;
    if (this._elapsedTime < 0) {
      // Elapsed time can be negative when there was a delay specified (which
      // sets elapsedTime to -delay).
      return;
    }
    if (this._interpolator == null) {
      this._interpolator = d3.interpolate(this.getObjectProperties(this._id)[this._propName], this._endValue);
    }
    t = Math.min(1, this._elapsedTime / this._duration);
    t = this._easeFunc(t);
    props = {};
    props[this._propName] = this._interpolator(t);
    // Update object properties.
    this.setObjectProperties(this._id, props);
    if (t >= 1) {
      // This ensures that 1 value is always reached.
      this.isFinished = true;
    }
  };

  /**
   * getObjectProperties method must be implemented by descendant!
   * Required interface:
   * @param  {*}      id Object ID, value passed
   *                     to .id() method will be used.
   * @return {Object} Properties hash.
   */
  PropertyTransition.prototype.getObjectProperties = null;

  /**
   * setObjectProperties method must be implemented by descendant!
   * Required interface:
   * @param  {*}      id Object ID, value passed
   *                     to .id() method will be used.
   * @param  {Object} props Properties hash.
   */
  PropertyTransition.prototype.setObjectProperties = null;

  /**
   * @private
   * @return {boolean} true when transition specification is incomplete.
   */
  PropertyTransition.prototype._incompleteSpec = function () {
    return this._id == null || this._propName == null || this._endValue == null;
  };

  return PropertyTransition;
});
/*global define */

define('models/md2d/models/atom-transition',['require','common/inherit','common/models/property-transition'],function (require) {

  var inherit            = require("common/inherit"),
      PropertyTransition = require("common/models/property-transition");

  function AtomTransition(model) {
    // Call super constructor.
    PropertyTransition.call(this);
    this._model = model;
  }
  inherit(AtomTransition, PropertyTransition);


  AtomTransition.prototype.setObjectProperties = function(id, props) {
    this._model.setAtomProperties(id, props);
  };

  AtomTransition.prototype.getObjectProperties = function (id) {
    return this._model.getAtomProperties(id);
  };

  return AtomTransition;
});

/*global define */

/**
  This plugin adds chemical reactions functionality to the MD2D engine.

  Datatable changes:
    atoms:
      sharedElectrons: an int representing the number of valence electrons currently shared
        with other atom. When sharedElectrons + valenceElectrons == 8 (or == 2 for helium), atom
        will no longer participate in chemical reactions.
*/

define('models/md2d/models/engine/plugins/chemical-reactions',['require','arrays','common/array-types','models/md2d/models/metadata','common/validator','models/md2d/models/engine/constants/index','models/md2d/models/engine/math/utils'],function(require) {
  var arrays             = require('arrays'),
      arrayTypes         = require('common/array-types'),
      metadata           = require('models/md2d/models/metadata'),
      validator          = require('common/validator'),
      constants          = require('models/md2d/models/engine/constants/index'),
      getAngleBetweenVec = require('models/md2d/models/engine/math/utils').getAngleBetweenVec,
      unit               = constants.unit,

      BOND_LEN_RATIO = 0.6, // follows Classic MW constant.
      // Based on the Classic MW constants defining bond style.
      BOND_TYPE = {
        1: 101, // single bond
        2: 107, // double bond
        3: 108  // tripe bond
      },
      BOND_TYPE_INV = (function() {
        var result = {};
        Object.keys(BOND_TYPE).forEach(function (key) {
          result[BOND_TYPE[key]] = key;
        });
        return result;
      }()),

      ANGULAR_BOND_STRENGTH = 50; // follows Classic MW default value.

  // Dot product of [x1, y1] and [x2, y2] vectors.
  function dot(x1, y1, x2, y2) {
    return x1 * x2 + y1 * y2;
  }

  // Returns true if array1 and array2 have at least one common element, false otherwise.
  function commonElement(array1, array2) {
    var len1 = array1.length,
        len2 = array2.length,
        i, j, el1;
    // It's a brutal force approach, but note that maximum length of the array is 4. We use that
    // function to compare if two atoms have a common bonded atom. In chemical reactions, only
    // 4 bonds can be formed per atom.
    for (i = 0; i < len1; i++) {
      el1 = array1[i];
      for (j = 0; j < len2; j++) {
        if (el1 === array2[j]) return true;
      }
    }
    return false;
  }

  return function ChemicalReactions(engine, _properties) {
    var api,

        properties         = validator.validateCompleteness(metadata.chemicalReactions, _properties),
        updateInterval     = properties.updateInterval,
        createAngularBonds = properties.createAngularBonds,
        noLoops            = properties.noLoops,
        valenceElectrons   = properties.valenceElectrons,
        bondEnergy         = properties.bondEnergy,
        activationEnergy   = properties.activationEnergy,
        bondProbability    = properties.bondProbability,

        // Helper array used only during bonds exchange process. When atom has radial bonds (one or
        // more), one of them (random) will be stored in this array. It will be exchanged with
        // free radical in case of collision.
        bondToExchange   = [],

        // Flag indicating whether bonds were changed during last calculations. If so, forces
        // recalculation should be triggered.
        bondsChanged = false,

        // Set of modified atoms, so atoms that have bonds added or removed.
        modifiedAtoms = {},

        atoms,
        elements,
        radialBonds,
        angularBonds;

    function updateAtomsTable() {
      var length = atoms.x.length;
      atoms.sharedElectrons = arrays.create(length, 0, arrayTypes.int8Type);
    }

    function updateRadialBondsTable() {
      var length = radialBonds.atom1.length;
      radialBonds.creationPotential = arrays.create(length, 0, arrayTypes.floatType);
    }

    function isRadical(i) {
      var v = valenceElectrons[atoms.element[i]],
          s = atoms.sharedElectrons[i];
      return (s < v) && (v + s < 8);
    }

    // Returns length of bond between elements i and j.
    function getBondLength(i, j) {
      return BOND_LEN_RATIO * (elements.sigma[i] + elements.sigma[j]);
    }

    // Returns strength of bond between elements i and j.
    function getBondStrength(i, j) {
      // In Classic MW bond strength is in units of eV per 0.01 nm. Convert to eV/nm (x 1e4) and use
      // the same method to calculate bond strength.
      return 2e4 * Math.sqrt(elements.epsilon[i] * elements.epsilon[j]);
    }

    function getUnpairedElectrons(i) {
      // Don't support quadruple bonds, as they are unrealistic and can cause problems
      // (e.g. there is :C=C:, but not C==C).
      var v = valenceElectrons[atoms.element[i]];
      return Math.min(Math.min(8 - v, v) - atoms.sharedElectrons[i]);
    }

    // Returns a number indicating whether a bond between atoms atom1 and atom2 would be
    // a single (1), double (2) or triple (3) bond.
    // Bond type will be based on two factors:
    // - available electron slots,
    // - probability, e.g. it's more likely than single bond will be formed instead of double or
    //   triple (see: bondProbability option)
    function getPossibleBondType(atom1, atom2, atom2Mod) {
      if (atom2Mod === undefined) atom2Mod = 0;
      var maxType = Math.min(3, getUnpairedElectrons(atom1), getUnpairedElectrons(atom2) + atom2Mod);
      // Only single bond is possible or no bond at all.
      if (maxType <= 1) {
        return maxType;
      } else {
        // Use probabilistic approach to choose a bond type.
        var prob = getBondTypeProbability(atoms.element[atom1], atoms.element[atom2]),
            randomValue = Math.random();

        if (maxType === 3) {
          if (randomValue < prob[0]) {
            return 1;
          } else if (randomValue < prob[0] + prob[1]) {
            return 2;
          } else {
            return 3;
          }
        } else { // maxType === 2
          // Note that when only single or double bond can be created, we add triple bond
          // likelihood to single bond likelihood! So for default 80% - 15% - 5% configuration,
          // we will end up with 85% for single bond and 15% for double bond.
          if (randomValue < prob[0] + prob[2]) {
            return 1;
          } else {
            return 2;
          }
        }
      }
    }

    // Returns a number indicating type of existing radial bond.
    function getBondType(bondIdx) {
      // Convert 101, 107, 108 into 1, 2, 3.
      var type = radialBonds.type[bondIdx] - 105;
      // We will get -4 for 101 and 2, 3 for 107, 108.
      return type < 0 ? 1 : type;
    }

    // TODO: (micro-)optimize functions below (?). They can be more elegant and require less work.

    // Returns bond chemical energy between elements i and j. Type indicates whether it's a single
    // (1 or undefined), double (2) or triple (3) bond.
    function getBondEnergy(i, j, type) {
      if (type === undefined) type = 1;
      switch(type) {
        case 1:
        return bondEnergy[i + "-" + j] != null ? bondEnergy[i + "-" + j] :
               bondEnergy[j + "-" + i] != null ? bondEnergy[j + "-" + i] :
                                                 bondEnergy["default"];
        case 2:
        return bondEnergy[i + "=" + j] != null ? bondEnergy[i + "=" + j] :
               bondEnergy[j + "=" + i] != null ? bondEnergy[j + "=" + i] :
                                                 getBondEnergy(i, j, 1) * 2;
        case 3:
        return bondEnergy[i + "#" + j] != null ? bondEnergy[i + "#" + j] :
               bondEnergy[j + "#" + i] != null ? bondEnergy[j + "#" + i] :
                                                  getBondEnergy(i, j, 1) * 3;
      }
    }

    // Returns activation energy when element i collides with j-k pair.
    function getActivationEnergy(i, j, k) {
            // order of j-k pair doesn't matter.
      return activationEnergy[i + "+" + j + "-" + k] != null ? activationEnergy[i + "+" + j + "-" + k] :
             activationEnergy[i + "+" + k + "-" + j] != null ? activationEnergy[i + "+" + k + "-" + j] :
                                                               activationEnergy["default"];
    }

    // Returns bond probability when element i collides with j.
    function getBondTypeProbability(i, j) {
            // order of j-k pair doesn't matter.
      return bondProbability[i + "-" + j] != null ? bondProbability[i + "-" + j] :
             bondProbability[j + "-" + i] != null ? bondProbability[j + "+" + i] :
                                                    bondProbability["default"];
    }


    // Returns energy needed to exchange bond between element i and j-k pair. So when collision
    // has bigger energy than returned value, bond should transform from j-k to i-j.
    function getEnergyForBondExchange(i, j, k, oldType, newType) {
      var oldEnergy = getBondEnergy(j, k, oldType),
          newEnergy = getBondEnergy(i, j, newType);

      if (newEnergy > oldEnergy) {
        // The final state is more stable, i-j bond (new) has more chemical energy than j-k (old).
        // Such transition should be easy, return just activation energy.
        return getActivationEnergy(i, j, k);
      } else {
        // The final state is less stable, i-j bond (new) is has less chemical energy than j-k (old).
        // Such transition should be harder, return activation energy and chemical energies
        // difference.
        return getActivationEnergy(i, j, k) + oldEnergy - newEnergy;
      }
    }

    function addRadialBond(props, a1, a2, bondType) {
      props.atom1 = a1;
      props.atom2 = a2;
      props.type = BOND_TYPE[bondType];
      engine.addRadialBond(props);
      bondsChanged = true;
      modifiedAtoms[a1] = true;
      modifiedAtoms[a2] = true;
      // Update shared electrons count.
      atoms.sharedElectrons[a1] += bondType;
      atoms.sharedElectrons[a2] += bondType;
      // In theory we can update bondToExchange with the newly created bond. However if we don't
      // do it, we won't exchange the bond in the same step we created it. It makes sense - things
      // will be clearer when e.g. user observes simulation in slow motion and tries to analyze
      // single step of chemical reaction.
    }

    function removeRadialBond(i, a1, a2, bondType) {
      engine.removeRadialBond(i, true);
      bondsChanged = true;
      modifiedAtoms[a1] = true;
      modifiedAtoms[a2] = true;
      // Update shared electrons count.
      atoms.sharedElectrons[a1] -= bondType;
      atoms.sharedElectrons[a2] -= bondType;
    }

    function transferRadialBond(i, props, a1, a2, a3, newType, oldType) {
      props.atom1 = a1;
      props.atom2 = a2;
      props.type = BOND_TYPE[newType];
      engine.setRadialBondProperties(i, props, true);
      bondsChanged = true;
      modifiedAtoms[a1] = true;
      modifiedAtoms[a2] = true;
      modifiedAtoms[a3] = true;
      // Update shared electrons count.
      atoms.sharedElectrons[a1] += newType;
      atoms.sharedElectrons[a2] += newType - oldType;
      atoms.sharedElectrons[a3] -= oldType;
      // a3 is no longer connected to bond with ID = bondIdx.
      if (bondToExchange[a3] === i) bondToExchange[a3] = undefined;
      // a2 is still connected to bond with ID = bondIdx, however if we set bondToExchange[a2]
      // to undefined, the same bond won't be transfered again during this step. It's not
      // necessary, but it will limit number of reactions during single step, making things
      // easier to observe and follow (otherwise the bond can exchanged multiple times during
      // one step, what can be confusing for users that will see only the final result).
      bondToExchange[a2] = undefined;
    }

    function addAngularBond(props) {
      engine.addAngularBond(props);
      bondsChanged = true;
    }

    function setAngularBondProperties(i, props) {
      engine.setAngularBondProperties(i, props);
      bondsChanged = true;
    }

    function cleanupModifiedAtomsAndFlags() {
      bondsChanged = false;
      Object.keys(modifiedAtoms).forEach(function (key) {
        delete modifiedAtoms[key];
      });
    }

    // TODO: we shouldn't have to do it explicitely at each step. Perhaps we should just modify add
    // and remove radial bond operations to make sure that sharedElectron count is always correct
    // (e.g. listen on approprieate events, but it's impossible at the moment).
    function validateSharedElectronsCount() {
      var type, i, len;
      for (i = 0, len = engine.getNumberOfAtoms(); i < len; i++) {
        atoms.sharedElectrons[i] = 0;
      }
      for (i = 0, len = engine.getNumberOfRadialBonds(); i < len; i++) {
        type = getBondType(i);
        atoms.sharedElectrons[radialBonds.atom1[i]] += type;
        atoms.sharedElectrons[radialBonds.atom2[i]] += type;
      }
    }

    function updateBondToExchangeArray() {
      var i, len;
      bondToExchange.length = 0;
      for (i = 0, len = engine.getNumberOfRadialBonds(); i < len; i++) {
        // Of course when a1 or a2 has more than one radial bond, only one will be saved.
        // However that's perfectly fine, as it's enough for bonds exchange mechanism.
        bondToExchange[radialBonds.atom1[i]] = bondToExchange[radialBonds.atom2[i]] = i;
      }
    }

    // When forcedCleanup equals to true, all bonds will be removed.
    function destroyBonds(forcedCleanup) {
      var i, a1, a2, el1, el2, dpot,
          xij, yij, ijsq, bondLen, bondType, chemEnergy,
          ljAdjustment,
          bondRemoved;

      i = 0;
      while (i < engine.getNumberOfRadialBonds()) {
        bondRemoved = false;

        a1 = radialBonds.atom1[i];
        a2 = radialBonds.atom2[i];
        bondLen = radialBonds.length[i];

        xij = atoms.x[a1] - atoms.x[a2];
        yij = atoms.y[a1] - atoms.y[a2];
        ijsq = xij * xij + yij * yij;

        dpot = Math.sqrt(ijsq) - bondLen;
        if (dpot > 0 || forcedCleanup) {
          // Bond is longer than its basic length, there is potential energy.
          dpot = 0.5 * radialBonds.strength[i] * dpot * dpot;
          // Bond chemical energy.
          el1 = atoms.element[a1];
          el2 = atoms.element[a2];
          bondType = getBondType(i);

          // increase in LJ potential energy caused by moving from (spot where bond formed)
          // to (spot where bond broken)
          ljAdjustment = -radialBonds.creationPotential[i] - engine.ljCalculator[el1][el2].potentialFromSquaredDistance(ijsq);
          chemEnergy = getBondEnergy(el1, el2, bondType);

          if (dpot - ljAdjustment > chemEnergy || forcedCleanup) {
            // Potential energy is larger than chemical energy, destroy bond.
            dpot -= chemEnergy;
            // LJ potential will now be calculated, take it into account.
            dpot -= ljAdjustment;

            if (engine.addKEToAtomPairAndConserveMomentum(dpot, a1, a2)) {
              removeRadialBond(i, a1, a2, bondType);
              bondRemoved = true;
            } else if (forcedCleanup) {
              // If it is impossible to add given amount of KE to just two atoms, try to add it
              // to all atoms. It's best what we can do to try to conserve energy during forced
              // cleanup.
              engine.addKEToAtoms(dpot);
              removeRadialBond(i, a1, a2, bondType);
              bondRemoved = true;
            }
          }
        }
        // After bond removal, all other bonds are re-indexed!
        if (!bondRemoved) i++;
      }
    }

    function createBonds(neighborList) {
      var N     = engine.getNumberOfAtoms(),
          nlist = neighborList.getList(),
          i, len,
          a1, a2,
          el1, el2,
          xi, yi, xij, yij, ijsq, bondLen;

      // Get all proximal pairs of atoms, using neighborList.
      for (a1 = 0; a1 < N; a1++) {
        el1 = atoms.element[a1];
        xi = atoms.x[a1];
        yi = atoms.y[a1];

        for (i = neighborList.getStartIdxFor(a1), len = neighborList.getEndIdxFor(a1); i < len; i++) {
          a2 = nlist[i];

          // Ignore bonded atoms.
          if (engine.atomsBonded(a1, a2)) continue;

          el2 = atoms.element[a2];
          xij = xi - atoms.x[a2];
          yij = yi - atoms.y[a2];

          ijsq = xij * xij + yij * yij;
          bondLen = BOND_LEN_RATIO * (elements.sigma[el1] + elements.sigma[el2]);

          if (ijsq < bondLen * bondLen) {
            // Distance is less than possible bond length, check if there will be a collision.
            collide(a1, a2, xij, yij, ijsq);
          }
        }
      }
    }

    function willCollide(a1, a2, xij, yij) {
      // Dot product is used to calculate cosinus of angle. Atoms are considered to be colliding
      // when they are going towards each other and angle between velocity vectors and vector that
      // connects both atoms is less than 90 degrees.
      return !(dot(atoms.vx[a1], atoms.vy[a1], xij, yij) >= 0.0 &&
               dot(atoms.vx[a2], atoms.vy[a2], xij, yij) <= 0.0);
    }

    function collide(a1, a2, xij, yij, ijsq) {
      var a1Radical, a2Radical, bothRadical, a1a2Exchange, a2a1Exchange;

      if (willCollide(a1, a2, xij, yij)) {
        a1Radical = isRadical(a1);
        a2Radical = isRadical(a2);
        bothRadical = a1Radical && a2Radical;
        a1a2Exchange = a1Radical && bondToExchange[a2] !== undefined;
        a2a1Exchange = a2Radical && bondToExchange[a1] !== undefined;

        if (bothRadical || a1a2Exchange || a2a1Exchange) {
          // Check if there is a common element in directly bonded atoms. If so, don't let the
          // chemical reaction happen, as we don't want to form triangles. Note that we defer
          // this check as much as it's possible, as it can be little bit expensive.
          if (commonElement(engine.getBondedAtoms(a1), engine.getBondedAtoms(a2))) return;
          // If "noLoops" mode is enabled, check whether a1 is a part of the same molecule as a2.
          if (noLoops && engine.getMoleculeAtoms(a2).indexOf(a1) !== -1) return;

          if (bothRadical) {
            // Simple case, two radicals, just create a new bond.
            makeBond(a1, a2, ijsq);
          } else if (a1a2Exchange) {
            tryToExchangeBond(a1, a2, bondToExchange[a2], xij, yij, ijsq);
          } else if (a2a1Exchange) {
            tryToExchangeBond(a2, a1, bondToExchange[a1], xij, yij, ijsq);
          }
        }
      }
    }

    function makeBond(a1, a2, ijsq) {
      var el1 = atoms.element[a1],
          el2 = atoms.element[a2],
          bondType = getPossibleBondType(a1, a2),
          en  = getBondEnergy(el1, el2, bondType),
          length, strength, dpot, creationPotential;

      if (en <= 0) return; // Fast path when bond energy is less than 0.

      length = getBondLength(el1, el2);
      strength = getBondStrength(el1, el2);

      // Energy conservation:
      // 1. Radial bond potential energy.
      dpot = Math.sqrt(ijsq) - length;
      dpot = -0.5 * strength * dpot * dpot;
      // 2. Bond chemical energy.
      dpot += en;

      // Account for LJ potential "removed" from the system by bond creation (because the engine
      // doesn't compute LJ force between bonded pairs)
      creationPotential = -engine.ljCalculator[el1][el2].potentialFromSquaredDistance(ijsq);

      if (creationPotential > 0) {
        // If creationPotential is > 0, a1 and a2 are impinging on the "LJ core". If we create
        // the bond, then when the bond breaks that potential will be released all at once via
        // addKEToAtoms and this eventually leads to model divergence.
        return;
      }

      if (engine.addKEToAtomPairAndConserveMomentum(dpot, a1, a2)) {
        addRadialBond({
          length: length,
          strength: strength,
          creationPotential: creationPotential
        }, a1, a2, bondType);
      }
    }

    function tryToExchangeBond(a1, a2, bondIdx, xij, yij, ijsq) {
      var el1 = atoms.element[a1],
          el2 = atoms.element[a2],
          a1Old = radialBonds.atom1[bondIdx],
          a2Old = radialBonds.atom2[bondIdx],
          el1Old = atoms.element[a1Old],
          el2Old = atoms.element[a2Old],
          // Atom that can lose the bond, it has to be different from a1 and a2.
          a3 = a1Old !== a1 && a1Old !== a2 ? a1Old : a2Old,
          el3 = atoms.element[a3],

          oldType = getBondType(bondIdx),
          // Take into account that a2 shared electron count is now affected by old bond!
          newType = getPossibleBondType(a1, a2, oldType),

          minCollisionEnergy = getEnergyForBondExchange(el1, el2, el3, oldType, newType),

          // Calculate the line-of-centers energy.
          ijsr = 1.0 / Math.sqrt(ijsq),
          vxij = atoms.vx[a1] - atoms.vx[a2],
          vyij = atoms.vy[a1] - atoms.vy[a2],
          vxy = vxij * xij * ijsr + vyij * yij * ijsr,

          a1Mass = atoms.mass[a1],
          a2Mass = atoms.mass[a2],

          collisionEnergy = constants.convert(a1Mass * a2Mass / (a1Mass + a2Mass) * vxy * vxy,
                            { from: unit.MW_ENERGY_UNIT, to: unit.EV }),

          newLength, newStrength, oldLength, oldStrength,
          lenDiff, dpot,
          ljAdjustment, creationPotential;

      // Use reduced mass to compute head-on kinetic energy.
      if (collisionEnergy > minCollisionEnergy) {
        // Kinetic energy is big enough to transfer radial bond.

        newLength = getBondLength(el1, el2);
        newStrength = getBondStrength(el1, el2);
        oldLength = radialBonds.length[bondIdx];
        oldStrength = radialBonds.strength[bondIdx];

        // Conserve energy.
        // New bond configuration.
        // 1. Radial bond potential energy.
        lenDiff = Math.sqrt(ijsq) - newLength;
        dpot = -0.5 * newStrength * lenDiff * lenDiff;
        // 2. Bond chemical energy.
        dpot += getBondEnergy(el1, el2, newType);
        creationPotential = -engine.ljCalculator[el1][el2].potentialFromSquaredDistance(ijsq);

        if (creationPotential > 0) {
          // as noted in makeBond, allowing the bond to be created now would cause trouble later
          return;
        }

        // Old bond configuration.
        xij = atoms.x[a1Old] - atoms.x[a2Old];
        yij = atoms.y[a1Old] - atoms.y[a2Old];
        ijsq = xij * xij + yij * yij;
        // 1. Radial bond potential energy.
        lenDiff = Math.sqrt(ijsq) - oldLength;
        dpot += 0.5 * oldStrength * lenDiff * lenDiff;
        // 2. Bond chemical energy.
        dpot -= getBondEnergy(el1Old, el2Old, oldType);

        // Account for difference in LJ potential between creation and release of old bond
        // (remember LJ force is not calculated between bonded pair)
        ljAdjustment = -radialBonds.creationPotential[bondIdx] - engine.ljCalculator[el1Old][el2Old].potentialFromSquaredDistance(ijsq);
        dpot -= ljAdjustment;

        if (engine.addKEToAtoms(dpot, a1, a2, a3)) {
          // Update bond, change it from a2-d3 to a1-a2.
          transferRadialBond(bondIdx, {
            length: newLength,
            strength: newStrength,
            creationPotential: creationPotential
          }, a1, a2, a3, newType, oldType);
        }
      }
    }

    function removeAngularBondOfModfiedAtoms() {
      // Remove all angular bonds that have modified atoms as a central atom (atom3 prop).
      // Use such "strange" form of loop, as while removing one bonds, other change their indexing.
      // So, after removal of bond 5, we should check bond 5 again, as it would be another bond
      // (previously indexed as 6).
      var i = 0;
      while (i < engine.getNumberOfAngularBonds()) {
        if (modifiedAtoms[angularBonds.atom3[i]]) {
          engine.removeAngularBond(i, true);
        } else {
          i++;
        }
      }
    }

    function tryToMakeAngularBondsStronger() {
      var i, len, a1, a2, a3, dpot;
      // Try to make angular bonds with strength equal to 0 stronger (if we can conserve energy).
      // Such bonds were added earlier when kinetic energy of atoms was too small to compensate
      // potential energy of angular bond. It's very likely to happen when bond between atoms is
      // much smaller or bigger than expected.
      for (i = 0, len = engine.getNumberOfAngularBonds(); i < len; i++) {
        if (angularBonds.strength[i] === 0) {
          a1 = angularBonds.atom1[i];
          a2 = angularBonds.atom2[i];
          a3 = angularBonds.atom3[i];
          dpot = angularBonds.angle[i] - getAngleBetweenAtoms(a1, a2, a3);
          dpot = -0.5 * ANGULAR_BOND_STRENGTH * dpot * dpot;

          if (engine.addKEToAtoms(dpot, a1, a2, a3)) {
            setAngularBondProperties(i, {
              strength: ANGULAR_BOND_STRENGTH
            });
          }
        }
      }
    }

    // Returns angle between a1-a3 and a2-a3 vectors.
    function getAngleBetweenAtoms(a1, a2, a3) {
      return getAngleBetweenVec(atoms.x[a1], atoms.y[a1],
                                atoms.x[a2], atoms.y[a2],
                                atoms.x[a3], atoms.y[a3]);
    }

    function getMoleculeAngle(valenceElectrons, sharedElectrons, bondsCount) {
      // Angle depends on number of regions of electric charge around atom. A region of electric
      // charge could be either an unshared pair of electrons or a shared pair (in a bond).
      // The regions of electron density will repel each other to minimize energy, so the following
      // angles are associated with regions of electron density:
      //
      // 4 regions = 90 degree angle
      // 3 regions = 120 degree angle
      // 2 regions = 180 degree angle
      //
      // There calculation about regions of electron density needs to consider the valence setting
      // for the central atom in a triplet grouping. Assuming single bonds this is what we get:
      // valence 1 = n/a (can't be a central atom in a triplet)
      // valence 2 = 2 regions
      // valence 3 = 3 regions
      // valence 4 = 4 regions
      // valence 5 = 4 regions (Two electrons will pair to become a shared pair and not bond. Three bonds possible.)
      // valence 6 = 4 regions (Two pairs of unshared electrons will occur. Two bonds possible.)
      // valence 7 = n/a (can't be a central atom in a triplet)
      // valence 8 = n/a (no bonds can form - this is a noble gas element)
      //
      // If double or triple bonds form then regions of electron density become merged, reducing
      // the total number of regions. Only atoms with valence 3 - 5 could be central atoms and
      // form double or triple bonds.
      // valence 3 with one double bond = 2 regions of electron density
      // valence 4 with one double bond = 3 regions of electron density
      // valence 4 with two double bonds = 2 regions of electron density
      // valence 4 with one triple bond = 2 regions of electron density
      // valence 5 with one double bond = 3 regions of electron density
      //
      // See related PT story: https://www.pivotaltracker.com/story/show/58090710
      //
      // Actually we can implement the whole algorithm using this simple formula:
      return 2 * Math.PI / (Math.min(4, valenceElectrons) - (sharedElectrons - bondsCount));
    }

    function setupNewAngularBonds() {
      var i, len, a1, a2, type, angle, ba, possibleBonds;
      // Construct helper structure containing bonded atoms.
      var bondedAtoms = [];
      for (i = 0, len = engine.getNumberOfRadialBonds(); i < len; i++) {
        a1 = radialBonds.atom1[i];
        a2 = radialBonds.atom2[i];
        type = BOND_TYPE_INV[radialBonds.type];
        if (modifiedAtoms[a1]) {
          if (!bondedAtoms[a1]) {
            bondedAtoms[a1] = [a2];
          } else {
            bondedAtoms[a1].push(a2);
          }
        }
        if (modifiedAtoms[a2]) {
          if (!bondedAtoms[a2]) {
            bondedAtoms[a2] = [a1];
          } else {
            bondedAtoms[a2].push(a1);
          }
        }
      }

      // Finally construct new angular bonds.
      for (i = 0, len = bondedAtoms.length; i < len; i++) {
        ba = bondedAtoms[i];
        if (!ba || ba.length < 2) continue;

        angle = getMoleculeAngle(valenceElectrons[atoms.element[i]], atoms.sharedElectrons[i], ba.length);

        if (ba.length === 2) {
          // e.g. A -- B -- A
          a1 = ba[0];
          a2 = ba[1];
          addAngularBondBetween(a1, a2, i, angle);
        } else {
          possibleBonds = [];
          if (ba.length === 3) {
            // e.g. A -- B -- A
            //           |
            //           A
            pushPossibleBond(possibleBonds, ba[0], ba[1], i);
            pushPossibleBond(possibleBonds, ba[0], ba[2], i);
            pushPossibleBond(possibleBonds, ba[1], ba[2], i);
          } else if (ba.length === 4) {
            // e.g.      A
            //           |
            //      A -- B -- A
            //           |
            //           A
            pushPossibleBond(possibleBonds, ba[0], ba[1], i);
            pushPossibleBond(possibleBonds, ba[0], ba[2], i);
            pushPossibleBond(possibleBonds, ba[0], ba[3], i);
            pushPossibleBond(possibleBonds, ba[1], ba[2], i);
            pushPossibleBond(possibleBonds, ba[1], ba[3], i);
            pushPossibleBond(possibleBonds, ba[2], ba[3], i);
          }
          possibleBonds.sort(comparePossibleBonds);
          createFirstNPossibleBonds(possibleBonds, ba.length - 1, angle);
        }
      }
    }

    function pushPossibleBond(arr, a1, a2, aCentral) {
      arr.push({
        angle: getAngleBetweenAtoms(a1, a2, aCentral),
        a1: a1,
        a2: a2,
        aCentral: aCentral
      });
    }

    function comparePossibleBonds(a, b) {
      if (a.angle < b.angle) return -1;
      if (a.angle > b.angle) return 1;
      return 0;
    }

    function createFirstNPossibleBonds(array, N, bondAngle) {
      var i, bondDef;
      for (i = 0; i < N; i++) {
        bondDef = array[i];
        addAngularBondBetween(bondDef.a1, bondDef.a2, bondDef.aCentral, bondAngle, bondDef.angle);
      }
    }

    function addAngularBondBetween(a1, a2, aCentral, bondAngle, currentAngle) {
      if (!currentAngle) {
        currentAngle = getAngleBetweenAtoms(a1, a2, aCentral);
      }
      var props = {
        atom1: a1,
        atom2: a2,
        atom3: aCentral,
        angle: bondAngle
      };
      // Calculate potential energy of angular bond.
      var dpot = bondAngle - currentAngle;
      dpot = -0.5 * ANGULAR_BOND_STRENGTH * dpot * dpot;
      // If we can't conserve energy, add a "fake" angular bond with strength equal to 0.
      // During next steps we will try to make it stronger when the angular bond potential
      // energy will be smaller of kinetic energy of atoms bigger.
      if (engine.addKEToAtoms(dpot, a1, a2, aCentral)) {
        props.strength = ANGULAR_BOND_STRENGTH;
      } else {
        props.strength = 0;
      }
      addAngularBond(props);
    }

    // Gets chemical potential energy stored in radial bonds.
    function getBondsChemicalPE() {
      var PE = 0,
          i, len;

      for (i = 0, len = engine.getNumberOfRadialBonds(); i < len; ++i) {
        PE -= (getBondEnergy(atoms.element[radialBonds.atom1[i]],
                            atoms.element[radialBonds.atom2[i]],
                            getBondType(i))
              - radialBonds.creationPotential[i]);
      }

      return PE;
    }

    // Public API.
    api = {
      initialize: function (dataTables) {
        atoms        = dataTables.atoms;
        elements     = dataTables.elements;
        radialBonds  = dataTables.radialBonds;
        angularBonds = dataTables.angularBonds;
        updateAtomsTable();
        updateRadialBondsTable();
      },

      performActionWithinIntegrationLoop: function (neighborList, dt, time) {
        // Below there is a stateless way to check if we should perform calculations or not.
        // Calculations should be performed every <update_interval> femtoseconds. In theory we could just
        // remember the last time we did calculations and then check if the current time is larger
        // (or equal) than the last + <interval>. However, due to tick history seeking, we can't make
        // an assumption that time value will be always bigger than during the previous function
        // execution.
        var mod = time % updateInterval;
        if (mod === 0 || (mod < dt && Math.floor(time / updateInterval) === Math.round(time / updateInterval))) {
          cleanupModifiedAtomsAndFlags();
          validateSharedElectronsCount();
          destroyBonds();
          // Update bondToExchange array after .destroyBonds() call! bondToExchange array is used
          // only by .createBonds() function anyway.
          updateBondToExchangeArray();
          createBonds(neighborList);

          if (createAngularBonds) {
            if (Object.keys(modifiedAtoms).length > 0) {
              // Some reaction took place, update angular bonds.
              removeAngularBondOfModfiedAtoms();
              tryToMakeAngularBondsStronger();
              setupNewAngularBonds();
            } else {
              // In fact this call can be placed before the whole if-else statement. However this
              // is a small optimization, as we can setup optimal order of function calls.
              tryToMakeAngularBondsStronger();
            }
          }

          if (bondsChanged) {
            // Update forces coming from new bonds configuration (!).
            engine.updateParticlesAccelerations();
          }
        }
      },

      // This function is required by the MD2D engine, so return empty array as
      // nothing has to be serialized in this plugin.
      getState: function () {
        return [];
      },

      processOutputState: function (state) {
        state.PE += getBondsChemicalPE();
      },

      /**
       * Sets bond energy (dissociation energy) of a bond.
       * @param {string} bondDescription e.g. "1-1" means single bond between element 1 and 1,
       *                                 "1=2" means double bond between element 1 and 2 etc.
       * @param {number} value           bond energy in eV..
       */
      setBondEnergy: function(bondDescription, value) {
        bondEnergy[bondDescription] = value;
      },

      /**
       * Sets valence electrons count of the given element.
       * @param {number} element
       * @param {number} value
       */
      setValenceElectrons: function(element, value) {
        valenceElectrons[element] = value;
        // Cleanup all bonds.
        destroyBonds(true);
        validateSharedElectronsCount();
      }
    };

    return api;
  };

});

/*global define: false, d3: false */

define('models/md2d/models/modeler',['require','common/performance','common/serialize','common/validator','common/lab-modeler-mixin','common/output-support','common/models/tick-history','models/md2d/models/engine/md2d','models/md2d/models/metadata','models/md2d/models/engine/constants/units','models/md2d/models/unit-definitions/index','models/md2d/models/units-translation','common/property-description','cs!models/md2d/models/solvent','models/md2d/models/aminoacids-props','cs!models/md2d/models/aminoacids-helper','models/md2d/models/engine/genetic-engine','models/md2d/models/performance-optimizer','models/md2d/models/atom-transition','underscore','models/md2d/models/engine/plugins/quantum-dynamics','models/md2d/models/engine/plugins/chemical-reactions'],function(require) {
  // Dependencies.
  var performance          = require('common/performance'),
      serialize            = require('common/serialize'),
      validator            = require('common/validator'),
      LabModelerMixin      = require('common/lab-modeler-mixin'),
      OutputSupport        = require('common/output-support'),
      TickHistory          = require('common/models/tick-history'),
      md2d                 = require('models/md2d/models/engine/md2d'),
      metadata             = require('models/md2d/models/metadata'),
      units                = require('models/md2d/models/engine/constants/units'),
      unitDefinitions      = require('models/md2d/models/unit-definitions/index'),
      UnitsTranslation     = require('models/md2d/models/units-translation'),
      PropertyDescription  = require('common/property-description'),
      Solvent              = require('cs!models/md2d/models/solvent'),
      aminoacids           = require('models/md2d/models/aminoacids-props'),
      aminoacidsHelper     = require('cs!models/md2d/models/aminoacids-helper'),
      GeneticEngine        = require('models/md2d/models/engine/genetic-engine'),
      PerformanceOptimizer = require('models/md2d/models/performance-optimizer'),
      AtomTransition       = require('models/md2d/models/atom-transition'),
      _ = require('underscore'),

      // plugins
      QuantumDynamics      = require('models/md2d/models/engine/plugins/quantum-dynamics'),
      ChemicalReactions    = require('models/md2d/models/engine/plugins/chemical-reactions'),

      md2dModelCount = 0;

  return function Model(initialProperties, initializationOptions, i18n) {

    // all models created with this constructor will be of type: "md2d"
    this.constructor.type = "md2d";

    initializationOptions = initializationOptions || {};

    var model = {},
        namespace = "md2dModel" + (++md2dModelCount),

        customSetters = {
          targetTemperature: function (value) {
            engine.setTargetTemperature(value);
          },

          temperatureControl: function(value) {
            engine.useThermostat(value);
          },

          lennardJonesForces: function(value) {
            engine.useLennardJonesInteraction(value);
          },

          coulombForces: function(value) {
            engine.useCoulombInteraction(value);
          },

          solventForceType: function(value) {
            engine.setSolventForceType(value);
          },

          DNAState: function(value) {
            engine.setDNAState(value);
          },

          solventForceFactor: function(value) {
            engine.setSolventForceFactor(value);
          },

          additionalSolventForceMult: function(value) {
            engine.setAdditionalSolventForceMult(value);
          },

          additionalSolventForceThreshold: function(value) {
            engine.setAdditionalSolventForceThreshold(value);
          },

          dielectricConstant: function(value) {
            engine.setDielectricConstant(value);
          },

          realisticDielectricEffect: function(value) {
            engine.setRealisticDielectricEffect(value);
          },

          VDWLinesCutoff: function(value) {
            var ratio = VDWLinesCutoffMap[value];
            if (ratio) {
              engine.setVDWLinesRatio(ratio);
            }
          },

          gravitationalField: function(value) {
            engine.setGravitationalField(value);
          },

          modelSampleRate: function() {
            if (!model.isStopped()) model.restart();
          },

          viscosity: function(value) {
            engine.setViscosity(value);
          },

          polarAAEpsilon: function (value) {
            var polarAAs, element1, element2,
                i, j, len;

            // Set custom pairwise LJ properties for polar amino acids.
            // They should attract stronger to better mimic nature.
            polarAAs = aminoacidsHelper.getPolarAminoAcids();
            for (i = 0, len = polarAAs.length; i < len; i++) {
              element1 = polarAAs[i];
              for (j = i + 1; j < len; j++) {
                element2 = polarAAs[j];
                // Set custom pairwise LJ epsilon (default one for AA is -0.1).
                engine.pairwiseLJProperties.set(element1, element2, {epsilon: value});
              }
            }
          },

          electricFieldDensity: function (value) {
            electricField.length = 0; // reuse existing array!
            if (!value) return;
            var minX = 0,
                minY = 0,
                maxX = model.properties.width,
                maxY = model.properties.height,
                diff = model.properties.width / value,
                yOffset = ((maxY / diff) % 1) * diff,
                y = minY + 0.5 * (yOffset + (diff <= maxY ? diff : 0)),
                x;
            while(y < maxY) {
              x = minX + 0.5 * diff;
              while(x < maxX) {
                electricField.push({x: x, y: y});
                x += diff;
              }
              y += diff;
            }
          }
        },

        // The set of units currently in effect. (Determined by the 'unitsScheme' property of the
        // model; default value is 'md2d')
        unitsDefinition = unitDefinitions.get(initialProperties.unitsScheme || 'md2d'),

        // Object that translates between 'native' md2d units and the units defined
        // by unitsDefinition.
        unitsTranslation = (function() {
          var scheme = initialProperties.unitsScheme || 'md2d';
          // If we're not using MD2D units, we need a translation (which, for each unit type, allows some
          // number of "native" MD2D units to stand for 1 translated unit, e.g., 1 nm represents 1m, with
          // the relationships between these ratios set up so that the calculations reamin physically
          // consistent.
          if (scheme !== 'md2d') {
            return new UnitsTranslation(unitsDefinition);
          }
          return undefined;
        }()),

        labModelerMixin = new LabModelerMixin({
          metadata: metadata,
          setters: customSetters,
          unitsDefinition: unitsDefinition,
          unitsTranslation: unitsTranslation,
          initialProperties: initialProperties
        }),

        dispatch = (function() {
          var d = labModelerMixin.dispatchSupport;
          d.addEventTypes("tick",
                          "addAtom", "removeAtom", "addRadialBond", "removeRadialBond",
                          "addElectricField", "removeElectricField", "changeElectricField",
                          "removeAngularBond", "textBoxesChanged", "imagesChanged");
          return d;
        }()),

        propertySupport = labModelerMixin.propertySupport,

        outputSupport,

        VDWLinesCutoffMap = {
          "short": 1.33,
          "medium": 1.67,
          "long": 2.0
        },
        defaultMaxTickHistory = 1000,
        newStep = false,
        lastSampleTime,
        sampleTimes = [],

        // FIXME: do we need global reference?
        modelState = window.state = {},
        tickHistory,

        // Transitions list.
        transitions = [],

        // Molecular Dynamics engine.
        engine = (function() {
          var e = md2d.createEngine();
          // Register invalidating change hooks.
          // pairwiseLJProperties object allows to change state which defines state of the whole simulation.
          e.pairwiseLJProperties.registerChangeHooks(propertySupport.invalidatingChangePreHook, propertySupport.invalidatingChangePostHook);
          return e;
        }()),

        // Genetic engine.
        geneticEngine,

        // An array of elements object.
        editableElements,

        // ######################### Main Data Structures #####################
        // They are initialized at the end of this function. These data strucutres
        // are mainly managed by the engine.

        // A hash of arrays consisting of arrays of element property values
        elements,

        // A hash of arrays consisting of arrays of obstacle property values
        obstacles,

        // A hash of arrays consisting of arrays of shape property values
        shapes,

        // A hash of arrays consisting of arrays of line property values
        lines,

        // A hash of arrays consisting of arrays of electric field property values
        electricFields,

        // A hash of arrays consisting of arrays of radial bond property values
        radialBonds,

        // A hash of arrays consisting of arrays of angular bond property values
        angularBonds,

        // A hash of arrays consisting of arrays of restraint property values
        // (currently atom-only)
        restraints,

        // ####################################################################

        // An array of objects consisting of atom index numbers and atom property values, for easy
        // consumption by the view. It is updated conservatively from the "unrolled" form used for
        // speedy computation by the engine.
        viewAtoms = [],

        // An array of objects consisting of radial bond index numbers and radial bond property
        // values, for easy consumption by the view.
        viewRadialBonds = [],

        // An array of objects consisting of photon index numbers and property values, for easy
        // consumption by the view. Only defined if the quantum dynamics plugin is used.
        viewPhotons,

        // An array of objects consisting of point coordinates and electric field force at that point
        // (e.g. [{ x: 1, y: 2, fx: 0.1, fy: 0.3 }, ...]).
        electricField = [],

        // The index of the "spring force" used to implement dragging of atoms in a running model
        liveDragSpringForceIndex = null,

        // Cached value of the 'friction' property of the atom being dragged in a running model
        liveDragSavedFriction,

        // Properties hashes for use by plugins
        pluginProperties;

    function processTransitions(timeDiff) {
      var i, len;
      model.startBatch();
      for (i = 0, len = transitions.length; i < len; i++) {
        transitions[i].process(timeDiff);
      }
      // Cleanup finished transitions.
      i = 0;
      while(i < transitions.length) {
        if (transitions[i].isFinished) {
          transitions.splice(i, 1);
        } else {
          i++;
        }
      }
      model.endBatch();
    }

    // Returns a copy of 'obj' with value replaced by fn(key, value) for every (key, value) pair.
    // (Underscore doesn't do this: https://github.com/documentcloud/underscore/issues/220)
    function mapValues(obj, fn) {
      obj = _.extend({}, obj);
      for (var k in obj) {
        if (obj.hasOwnProperty(k)) obj[k] = fn(k, obj[k]);
      }
      return obj;
    }

    // Modifies a properties hash which has translated units to have MD2D units. Leaves properties
    // without a unitType (or with an unrecognized unitType) unmodified.
    // Returns 'properties' unmodified (not a copy) if there is no units translation in effect.
    function translateToMD2DUnits(properties, metadata) {
      if (!unitsTranslation) return properties;
      return mapValues(properties, function(key, value) {
        return unitsTranslation.translateToModelUnits(value, metadata[key] && metadata[key].unitType);
      });
    }

    // Modifies a properties hash which has MD2D units to have translated units. Leaves properties
    // without a unitType (or with an unrecognized unitType) unmodified.
    // Returns 'properties' unmodified (not a copy) if there is no units translation in effect.
    function translateFromMD2DUnits(properties, metadata) {
      if (!unitsTranslation) return properties;
      return mapValues(properties, function(key, value) {
        return unitsTranslation.translateFromModelUnits(value, metadata[key] && metadata[key].unitType);
      });
    }

    /**
      Executes the closure 'extract' which extracts from the tick history, then dispatches
      addAtom/removeAtom, etc, events as needed.

      This prevents unneessary creation and removal of atoms.
    */
    var runAndDispatchObjectNumberChanges = (function() {
      var objects = [{
        getNum: 'getNumberOfAtoms',
        addEvent: 'addAtom',
        removeEvent: 'removeAtom'
      }, {
        getNum: 'getNumberOfRadialBonds',
        addEvent: 'addRadialBond',
        removeEvent: 'removeRadialBond'
      }];

      return function (extract) {
        var i, o, newNum;
        for (i = 0; i < objects.length; i++) {
          o = objects[i];
          o.num = engine[o.getNum]();
        }

        extract();

        for (i = 0; i < objects.length; i++) {
          o = objects[i];
          newNum = engine[o.getNum]();
          if (newNum > o.num) {
            dispatch[o.addEvent]();
          } else if (newNum < o.num) {
            dispatch[o.removeEvent]();
          }
        }
      };
    })();

    /**
      This method is called to refresh the viewAtoms array and macrostate variables (KE, PE,
      temperature) whenever an engine integration occurs or the model state is otherwise changed.

      Normally, you should call the methods updateOutputPropertiesAfterChange or
      updateAllOutputProperties rather than calling this method. Calling this method directly does
      not cause output-property listeners to be notified, and calling it prematurely will confuse
      the detection of changed properties.
    */
    function readModelState() {
      engine.updateParticlesAccelerations();
      engine.computeOutputState(modelState);
      // remember that getViewPhotons will eventually be a modeler-layer method that ingests a raw
      // representation provided by modelState.photons
      viewPhotons = engine.callPluginAccessor('getViewPhotons');
      updateViewAtoms(modelState.atoms);
      updateViewRadialBonds(modelState.radialBonds, modelState.atoms);
      updateViewElectricField();
    }

    // Transpose 'atoms' object into 'viewAtoms' for consumption by view code
    function updateViewAtoms(atoms) {
      var n = engine.getNumberOfAtoms(),
          i,
          prop,
          amino,
          viewAtom;

      // TODO: refactor whole approach to creation of objects from flat arrays.
      // Think about more general way of detecting and representing amino acids.
      // However it would be reasonable to perform such refactoring later, when all requirements
      // related to proteins engine are clearer.

      viewAtoms.length = n;

      for (i = 0, n; i < n; i++) {
        if (!viewAtoms[i]) {
          viewAtoms[i] = {
            idx: i
          };
        }
        viewAtom = viewAtoms[i];

        for (prop in atoms) {
          if (atoms.hasOwnProperty(prop)) {
            viewAtom[prop] = atoms[prop][i];
          }
        }

        viewAtom.aminoAcid = aminoacidsHelper.isAminoAcid(atoms.element[i]);
        if (viewAtom.aminoAcid) {
          amino = aminoacidsHelper.getAminoAcidByElement(atoms.element[i]);
          viewAtom.symbol = amino.symbol;
          viewAtom.label  = amino.abbreviation;
        }
      }
    }

    function updateViewRadialBonds(radialBonds, atoms) {
      var n = engine.getNumberOfRadialBonds(),
          viewBond, prop, i;

      viewRadialBonds.length = n;

      for (i = 0; i < n; i++) {
        if (!viewRadialBonds[i]) {
          viewRadialBonds[i] = {
            idx: i
          };
        }
        viewBond = viewRadialBonds[i];

        for (prop in radialBonds) {
          viewBond[prop] = radialBonds[prop][i];
        }

        // Additionally calculate x1, y1, x2, y2 properties that are useful for view.
        viewBond.x1 = atoms.x[viewBond.atom1];
        viewBond.y1 = atoms.y[viewBond.atom1];
        viewBond.x2 = atoms.x[viewBond.atom2];
        viewBond.y2 = atoms.y[viewBond.atom2];
      }
    }

    function updateViewElectricField() {
      // It may seem strange that model reads "viewOption"
      // ("showElectricField"), but this is definitely reasonable
      // optimization.
      if (!electricField.length || !model.properties.showElectricField) return;
      var i, len, p;
      for (i = 0, len = electricField.length; i < len; i++) {
        p = electricField[i];
        engine.getCoulombForceAt(p.x, p.y, p);
      }
    }

    /**
      return a random element index ... which is *not* an amino acid element
    */
    function randomElement() {
      var len = engine.getNumberOfElements(),
          el = Math.floor( Math.random() * len );
      while(aminoacidsHelper.isAminoAcid(el)) {
        el = Math.floor( Math.random() * len );
      }
      return el;
    }

    /**
      Create set of amino acids elements. Use descriptions
      provided in 'aminoacids' array.
    */
    function createAminoAcids() {
      var sigmaIn01Angstroms,
          sigmaInNm,
          i, len;

      // Note that amino acids ALWAYS have IDs from
      // AMINO_ELEMENT_FIRST_IDX (= 5) to AMINO_ELEMENT_LAST_IDX (= 24).
      // This is enforced by backward compatibility with Classic MW.

      // At the beginning, ensure that elements from 0 to 24 exists.
      for (i = engine.getNumberOfElements(); i <= aminoacidsHelper.lastElementID; i++) {
        model.addElement({
          id: i
        });
      }

      // Set amino acids properties using elements from 5 to 24.
      for (i = 0, len = aminoacids.length; i < len; i++) {
        // Note that sigma is calculated using Classic MW approach.
        // See: org.concord.mw2d.models.AminoAcidAdapter
        // Basic length unit in Classic MW is 0.1 Angstrom.
        sigmaIn01Angstroms = 18 * Math.pow(aminoacids[i].volume / aminoacids[0].volume, 0.3333333333333);
        sigmaInNm = units.convert(sigmaIn01Angstroms / 10, { from: units.unit.ANGSTROM, to: units.unit.NANOMETER });
        // Use engine's method instead of modeler's method to avoid validation.
        // Modeler's wrapper ensures that amino acid is immutable, so it won't allow
        // to set properties of amino acid.
        engine.setElementProperties(aminoacidsHelper.firstElementID + i, {
          mass: aminoacids[i].molWeight,
          sigma: sigmaInNm
          // Don't provide epsilon, as default value should be used.
          // Classic MW uses epsilon 0.1 for all amino acids, which is default one.
          // See: org.concord.mw2d.models.AtomicModel.resetElements()
        });
      }
    }

    // ------------------------------------------------------------
    //
    // Public functions
    //
    // ------------------------------------------------------------

    /**
      Current seek position
    */
    model.stepCounter = function() {
      return tickHistory.get("counter");
    };

    /**
      Current position of first value in tick history, normally this will be 0.
      This will be greater than 0 if maximum size of tick history has been exceeded.
    */
    model.stepStartCounter = function() {
      return tickHistory.get("startCounter");
    };

    /** Total number of ticks that have been run & are stored, regardless of seek
        position
    */
    model.steps = function() {
      return tickHistory.get("length");
    };

    model.isNewStep = function() {
      return newStep;
    };

    model.seek = function(location) {
      if (!arguments.length) { location = 0; }
      if (!model.isStopped()) {
        model.stop();
      }
      newStep = false;
      runAndDispatchObjectNumberChanges(function() {
        tickHistory.seekExtract(location);
        readModelState();
        model.updateAllOutputProperties();
        dispatch.seek();
      });
      return tickHistory.get("counter");
    };

    model.stepBack = function(num) {
      if (!arguments.length) { num = 1; }
      if (!model.isStopped()) {
        model.stop();
      }
      newStep = false;
      runAndDispatchObjectNumberChanges(function() {
        var i, index;
        i = -1; while(++i < num) {
          index = tickHistory.get("index");
          if (index > 0) {
            tickHistory.decrementExtract();
            readModelState();
            model.updateAllOutputProperties();
            dispatch.stepBack();
          }
        }
      });
      return tickHistory.get("counter");
    };

    model.stepForward = function(num) {
      if (!arguments.length) { num = 1; }
      if (!model.isStopped()) {
        model.stop();
      }
      runAndDispatchObjectNumberChanges(function() {
        var i, index, size;
        i=-1; while(++i < num) {
          index = tickHistory.get("index");
          size = tickHistory.get("length");
          if (index < size-1) {
            tickHistory.incrementExtract();
            readModelState();
            model.updateAllOutputProperties();
            dispatch.stepForward();
          } else {
            model.tick();
          }
        }
      });
      return tickHistory.get("counter");
    };

    /**
      Initialize minX, minYm, maxX, maxY from width and height
      when these options are undefined.
    */
    function initializeDimensions(properties) {
      var minX = properties.minX,
          minY = properties.minY,
          maxX = properties.maxX,
          maxY = properties.maxY;

      properties.minX = minX != null ? minX : 0;
      properties.maxX = maxX != null ? maxX : properties.width;
      properties.minY = minY != null ? minY : 0;
      properties.maxY = maxY != null ? maxY : properties.height;
    }

    /**
      Creates a new md2d engine and leaves it in 'engine'.
    */
    function initializeEngine(properties, pluginProperties) {
      engine.setDimensions([properties.minX, properties.minY, properties.maxX, properties.maxY]);

      if (pluginProperties.quantumDynamics) {
        properties.useQuantumDynamics = true;
        engine.addPlugin(new QuantumDynamics(engine, pluginProperties.quantumDynamics));
      } else {
        properties.useQuantumDynamics = false;
      }

      if (pluginProperties.chemicalReactions) {
        properties.useChemicalReactions = true;
        engine.addPlugin(new ChemicalReactions(engine, pluginProperties.chemicalReactions));
      } else {
        properties.useChemicalReactions = false;
      }

      // Copy reference to basic properties.
      // FIXME. This should go away. https://www.pivotaltracker.com/story/show/50086079
      elements          = engine.elements;
      radialBonds       = engine.radialBonds;
      angularBonds      = engine.angularBonds;
      restraints        = engine.restraints;
      obstacles         = engine.obstacles;
      shapes            = engine.shapes;
      lines             = engine.lines;
      electricFields = engine.electricFields;
    }

    model.createElements = function(_elements) {
      var i, num, prop, elementProps;

      // Start batch process
      model.startBatch();

      if (_elements === undefined) {
        // Special case when elements are not defined.
        // Empty object will be filled with default values.
        model.addElement({id: 0});
        model.endBatch();
        return;
      }

      // _elements is hash of arrays (as specified in JSON model).
      // So, for each index, create object containing properties of
      // element 'i'. Later, use these properties to add element
      // using basic addElement method.
      for (i = 0, num = _elements.mass.length; i < num; i++) {
        elementProps = {};
        for (prop in _elements) {
          if (_elements.hasOwnProperty(prop)) {
            elementProps[prop] = _elements[prop][i];
          }
        }
        model.addElement(elementProps);
      }

      // End batch process
      model.endBatch();

      return model;
    };

    /**
      Creates a new set of atoms.

      config: a hash specifying the x,y,vx,vy properties of the atoms
    */
    model.createAtoms = function(config) {
          // Options for addAtom method.
      var options = {
            // Do not check the position of atom, assume that it's valid.
            suppressCheck: true
          },
          i, num, prop, atomProps;

      dispatch.willReset();

      // Start batch process
      model.startBatch();

      num = config.x.length;

      // config is hash of arrays (as specified in JSON model). So, for each index, create object
      // containing properties of atom 'i'. Later, use these properties to add atom using basic
      // addAtom method.
      for (i = 0; i < num; i++) {
        atomProps = {};
        for (prop in config) {
          if (config.hasOwnProperty(prop)) {
            atomProps[prop] = config[prop][i];
          }
        }
        model.addAtom(atomProps, options);
      }

      // End batch process
      model.endBatch();

      // Listeners should consider resetting the atoms a 'reset' event
      dispatch.reset();
    };

    model.createRadialBonds = function(_radialBonds) {
      var num = _radialBonds.strength.length,
          i, prop, radialBondProps;

      // Start batch process
      model.startBatch();

      // _radialBonds is hash of arrays (as specified in JSON model).
      // So, for each index, create object containing properties of
      // radial bond 'i'. Later, use these properties to add radial bond
      // using basic addRadialBond method.
      for (i = 0; i < num; i++) {
        radialBondProps = {};
        for (prop in _radialBonds) {
          if (_radialBonds.hasOwnProperty(prop)) {
            radialBondProps[prop] = _radialBonds[prop][i];
          }
        }
        model.addRadialBond(radialBondProps);
      }

      // End batch process
      model.endBatch();

      return model;
    };

    model.createAngularBonds = function(_angularBonds) {
      var num = _angularBonds.strength.length,
          i, prop, angularBondProps;

      // Start batch process
      model.startBatch();

      // _angularBonds is hash of arrays (as specified in JSON model).
      // So, for each index, create object containing properties of
      // angular bond 'i'. Later, use these properties to add angular bond
      // using basic addAngularBond method.
      for (i = 0; i < num; i++) {
        angularBondProps = {};
        for (prop in _angularBonds) {
          if (_angularBonds.hasOwnProperty(prop)) {
            angularBondProps[prop] = _angularBonds[prop][i];
          }
        }
        model.addAngularBond(angularBondProps);
      }

      // End batch process
      model.endBatch();

      return model;
    };

    model.createRestraints = function(_restraints) {
      var num = _restraints.atomIndex.length,
          i, prop, restraintsProps;

      // _restraints is hash of arrays (as specified in JSON model).
      // So, for each index, create object containing properties of
      // restraint 'i'. Later, use these properties to add restraint
      // using basic addRestraint method.
      for (i = 0; i < num; i++) {
        restraintsProps = {};
        for (prop in _restraints) {
          if (_restraints.hasOwnProperty(prop)) {
            restraintsProps[prop] = _restraints[prop][i];
          }
        }
        model.addRestraint(restraintsProps);
      }

      return model;
    };

    model.createObstacles = function(_obstacles) {
      var numObstacles = _obstacles.x.length,
          i, prop, obstacleProps;

      // _obstacles is hash of arrays (as specified in JSON model).
      // So, for each index, create object containing properties of
      // obstacle 'i'. Later, use these properties to add obstacle
      // using basic addObstacle method.
      for (i = 0; i < numObstacles; i++) {
        obstacleProps = {};
        for (prop in _obstacles) {
          if (_obstacles.hasOwnProperty(prop)) {
            obstacleProps[prop] = _obstacles[prop][i];
          }
        }
        model.addObstacle(obstacleProps);
      }

      return model;
    };

    model.createShapes = function(_shapes) {
      var numShapes = _shapes.x.length,
          i, prop, shapeProps;

      // See function above
      for (i = 0; i < numShapes; i++) {
        shapeProps = {};
        for (prop in _shapes) {
          if (_shapes.hasOwnProperty(prop)) {
            shapeProps[prop] = _shapes[prop][i];
          }
        }
        model.addShape(shapeProps);
      }

      return model;
    };

    model.createLines = function(_lines) {
      var numLines = _lines.x1.length,
          i, prop, lineProps;

      // See function above
      for (i = 0; i < numLines; i++) {
        lineProps = {};
        for (prop in _lines) {
          if (_lines.hasOwnProperty(prop)) {
            lineProps[prop] = _lines[prop][i];
          }
        }
        model.addLine(lineProps);
      }

      return model;
    };

    model.createElectricFields = function(_eFields) {
      model.batch(function () {
        var count = _eFields.intensity.length,
                i, prop, eFieldProps;

        for (i = 0; i < count; i++) {
          eFieldProps = {};
          for (prop in _eFields) {
            if (_eFields.hasOwnProperty(prop)) {
              eFieldProps[prop] = _eFields[prop][i];
            }
          }
          model.addElectricField(eFieldProps);
        }
      });
      return model;
    };

    // Beware. The "reset" button in Lab interactives do not call this method. Instead they "reload"
    // the model, discarding this model object and creating a new one from the model JSON.
    model.reset = function() {
      dispatch.willReset();
      propertySupport.invalidatingChangePreHook();
      engine.setTime(0);
      tickHistory.restoreInitialState();
      propertySupport.invalidatingChangePostHook();
      model.resetAllOutputProperties();
      dispatch.reset();
    };

    model.getTotalMass = function() {
      return engine.getTotalMass();
    };

    model.getAtomKineticEnergy = function(i) {
      return engine.getAtomKineticEnergy(i);
    };

    /**
      Attempts to add an 0-velocity atom to a random location. Returns false if after 10 tries it
      can't find a location. (Intended to be exposed as a script API method.)

      Optionally allows specifying the element (default is to randomly select from all editableElements) and
      charge (default is neutral).
    */
    model.addRandomAtom = function(el, charge) {
      if (el == null) el = randomElement();
      if (charge == null) charge = 0;

      var width = model.get('width'),
          height = model.get('height'),
          minX = model.get('minX'),
          minY = model.get('minY'),
          radius = engine.getRadiusOfElement(el),
          x,
          y,
          loc,
          numTries = 0,
          // try at most ten times.
          maxTries = 10;

      do {
        x = minX + Math.random() * width - 2*radius;
        y = minY + Math.random() * height - 2*radius;

        // findMinimimuPELocation will return false if minimization doesn't converge, in which case
        // try again from a different x, y
        loc = engine.findMinimumPELocation(el, x, y, 0, 0, charge);
        if (loc && model.addAtom({ element: el, x: loc[0], y: loc[1], charge: charge })) return true;
      } while (++numTries < maxTries);

      return false;
    };

    /**
      Adds a new atom defined by properties.
      Intended to be exposed as a script API method also.

      Adjusts (x,y) if needed so that the whole atom is within the walls of the container.

      Returns false and does not add the atom if the potential energy change of adding an *uncharged*
      atom of the specified element to the specified location would be positive (i.e, if the atom
      intrudes into the repulsive region of another atom), or if atom is placed inside an obstacle

      Otherwise, returns true.

      silent = true disables this check.
    */
    model.addAtom = function(props, options) {
      var minX = model.get('minX'),
          minY = model.get('minY'),
          maxX = model.get('maxX'),
          maxY = model.get('maxY'),
          radius;

      options = options || {};

      // Validate properties, provide default values.
      props = validator.validateCompleteness(metadata.atom, props);

      // As a convenience to script authors, bump the atom within bounds
      radius = engine.getRadiusOfElement(props.element);
      if (props.x < (minX + radius)) props.x = minX + radius;
      if (props.x > (maxX - radius)) props.x = maxX - radius;
      if (props.y < (minY + radius)) props.y = minY + radius;
      if (props.y > (maxY - radius)) props.y = maxY - radius;

      // check the potential energy change caused by adding an *uncharged* atom at (x,y)
      if (!options.suppressCheck && !engine.canPlaceAtom(props.element, props.x, props.y, null, model.get('skipPECheckOnAddAtom'))) {
        // return false on failure
        return false;
      }

      propertySupport.invalidatingChangePreHook();

      engine.addAtom(props);

      propertySupport.invalidatingChangePostHook();

      dispatch.addAtom();

      return true;
    };

    model.removeAtom = function(i) {
      var prevRadBondsCount = engine.getNumberOfRadialBonds(),
          prevAngBondsCount = engine.getNumberOfAngularBonds();

      propertySupport.invalidatingChangePreHook();
      engine.removeAtom(i);
      // Enforce modeler to recalculate viewAtoms array.
      viewAtoms.length = 0;
      propertySupport.invalidatingChangePostHook();

      // Notify listeners that atoms is removed.
      dispatch.removeAtom();

      // Removing of an atom can also cause removing of
      // the connected radial bond. Detect it and notify listeners.
      if (engine.getNumberOfRadialBonds() !== prevRadBondsCount) {
        dispatch.removeRadialBond();
      }
      if (engine.getNumberOfAngularBonds() !== prevAngBondsCount) {
        dispatch.removeAngularBond();
      }
    };

    model.addElement = function(props) {
      // Validate properties, use default values if there is such need.
      props = validator.validateCompleteness(metadata.element, props);
      // Finally, add radial bond.
      engine.addElement(props);
    };

    model.addObstacle = function(props) {
      var validatedProps;
      // Validate properties, use default values if there is such need.
      validatedProps = validator.validateCompleteness(metadata.obstacle, props);
      // Finally, add obstacle.
      propertySupport.invalidatingChangePreHook();
      engine.addObstacle(validatedProps);
      propertySupport.invalidatingChangePostHook();
    };

    model.removeObstacle = function (idx) {
      propertySupport.invalidatingChangePreHook();
      engine.removeObstacle(idx);
      propertySupport.invalidatingChangePostHook();
    };

    model.addShape = function(props) {
      var validatedProps;
      // Validate properties, use default values if there is such need.
      validatedProps = validator.validateCompleteness(metadata.shape, props);
      // Finally, add shape.
      propertySupport.invalidatingChangePreHook();
      engine.addShape(validatedProps);
      propertySupport.invalidatingChangePostHook();
    };

    model.removeShape = function (idx) {
      var prevElFieldsCount = engine.getNumberOfElectricFields();

      propertySupport.invalidatingChangePreHook();
      engine.removeShape(idx);
      propertySupport.invalidatingChangePostHook();

      if (engine.getNumberOfElectricFields() !== prevElFieldsCount) {
        dispatch.removeElectricField();
      }
      //TODO FIXME: also .removeShape() event should be dispatched.
    };

    model.addLine = function(props) {
      var validatedProps;
      // Validate properties, use default values if there is such need.
      validatedProps = validator.validateCompleteness(metadata.line, props);
      // Finally, add line.
      propertySupport.invalidatingChangePreHook();
      engine.addLine(validatedProps);
      propertySupport.invalidatingChangePostHook();
    };

    model.removeLine = function (idx) {
      //var prevElFieldsCount = engine.getNumberOfElectricFields();

      propertySupport.invalidatingChangePreHook();
      engine.removeLine(idx);
      propertySupport.invalidatingChangePostHook();
      //TODO FIXME: also .removeLine() event should be dispatched.
    };

    model.addElectricField = function(props) {
      var validatedProps;
      // Validate properties, use default values if there is such need.
      validatedProps = validator.validateCompleteness(metadata.electricField, props);
      // Finally, add shape.
      propertySupport.invalidatingChangePreHook();
      engine.addElectricField(validatedProps);
      propertySupport.invalidatingChangePostHook();
      dispatch.addElectricField();
    };

    model.removeElectricField = function (idx) {
      propertySupport.invalidatingChangePreHook();
      engine.removeElectricField(idx);
      propertySupport.invalidatingChangePostHook();
      dispatch.removeElectricField();
    };

    model.addRadialBond = function(props) {
      props = validator.validateCompleteness(metadata.radialBond, props);
      propertySupport.invalidatingChangePreHook();
      engine.addRadialBond(props);
      propertySupport.invalidatingChangePostHook();
      dispatch.addRadialBond();
    };

    model.removeRadialBond = function(idx) {
      propertySupport.invalidatingChangePreHook();
      engine.removeRadialBond(idx);
      propertySupport.invalidatingChangePostHook();
      dispatch.removeRadialBond();
    };

    model.addAngularBond = function(props) {
      props = validator.validateCompleteness(metadata.angularBond, props);
      propertySupport.invalidatingChangePreHook();
      engine.addAngularBond(props);
      propertySupport.invalidatingChangePostHook();
    };

    model.removeAngularBond = function(idx) {
      propertySupport.invalidatingChangePreHook();
      engine.removeAngularBond(idx);
      propertySupport.invalidatingChangePostHook();
      dispatch.removeAngularBond();
    };

    model.addRestraint = function(props) {
      // Validate properties, use default values if there is such need.
      props = validator.validateCompleteness(metadata.restraint, props);
      // Finally, add restraint.
      propertySupport.invalidatingChangePreHook();
      engine.addRestraint(props);
      propertySupport.invalidatingChangePostHook();
    };

    /** Return the bounding box of the molecule containing atom 'atomIndex', with atomic radii taken
        into account.

       @returns an object with properties 'left', 'right', 'top', and 'bottom'. These are translated
       relative to the center of atom 'atomIndex', so that 'left' represents (-) the distance in nm
       between the leftmost edge and the center of atom 'atomIndex'.
    */
    model.getMoleculeBoundingBox = function(atomIndex) {

      var atoms = modelState.atoms,
          moleculeAtoms,
          i,
          x,
          y,
          r,
          top = -Infinity,
          left = Infinity,
          bottom = Infinity,
          right = -Infinity,
          cx,
          cy;

      moleculeAtoms = engine.getMoleculeAtoms(atomIndex);
      moleculeAtoms.push(atomIndex);

      for (i = 0; i < moleculeAtoms.length; i++) {
        x = atoms.x[moleculeAtoms[i]];
        y = atoms.y[moleculeAtoms[i]];
        r = atoms.radius[moleculeAtoms[i]];

        if (x-r < left  ) left   = x-r;
        if (x+r > right ) right  = x+r;
        if (y-r < bottom) bottom = y-r;
        if (y+r > top   ) top    = y+r;
      }

      cx = atoms.x[atomIndex];
      cy = atoms.y[atomIndex];

      return { top: top-cy, left: left-cx, bottom: bottom-cy, right: right-cx };
    };

    model.setTemperatureOfAtoms = function(atomIndices, T) {
      propertySupport.invalidatingChangePreHook();
      engine.setTemperatureOfAtoms(atomIndices, T);
      propertySupport.invalidatingChangePostHook();
    };

    model.addKEToAtoms = function(energy, atomIndices) {
      propertySupport.invalidatingChangePreHook();

      if (atomIndices == null) {
        engine.addKEToAtoms(energy);
      } else {
        // This function inside engine has a slightly different API.
        engine.addKEToAtoms.apply(engine, [energy].concat(atomIndices));
      }

      propertySupport.invalidatingChangePostHook();
    };

    model.getTemperatureOfAtoms = function(atomIndices) {
      return engine.getTemperatureOfAtoms(atomIndices);
    };

    /**
        A generic method to set properties on a single existing atom.

        Example: setAtomProperties(3, {x: 5, y: 8, px: 0.5, charge: -1})

        This can optionally check the new location of the atom to see if it would
        overlap with another another atom (i.e. if it would increase the PE).

        This can also optionally apply the same dx, dy to any atoms in the same
        molecule (if x and y are being changed), and check the location of all
        the bonded atoms together.
      */
    model.setAtomProperties = function(i, props, checkLocation, moveMolecule, skipPECheck) {
      var atoms = modelState.atoms,
          moleculeAtoms,
          dx, dy,
          new_x, new_y,
          j, jj;

      // Validate properties.
      props = validator.validate(metadata.atom, props);

      if (moveMolecule) {
        moleculeAtoms = engine.getMoleculeAtoms(i);
        if (moleculeAtoms.length > 0) {
          dx = typeof props.x === "number" ? props.x - atoms.x[i] : 0;
          dy = typeof props.y === "number" ? props.y - atoms.y[i] : 0;
          for (j = 0, jj=moleculeAtoms.length; j<jj; j++) {
            new_x = atoms.x[moleculeAtoms[j]] + dx;
            new_y = atoms.y[moleculeAtoms[j]] + dy;
            if (!model.setAtomProperties(moleculeAtoms[j], {x: new_x, y: new_y}, checkLocation, false)) {
              return false;
            }
          }
        }
      }

      if (checkLocation) {
        var x  = typeof props.x === "number" ? props.x : atoms.x[i],
            y  = typeof props.y === "number" ? props.y : atoms.y[i],
            el = typeof props.element === "number" ? props.y : atoms.element[i];

        if (!engine.canPlaceAtom(el, x, y, i, skipPECheck)) {
          return false;
        }
      }

      propertySupport.invalidatingChangePreHook();
      engine.setAtomProperties(i, translateToMD2DUnits(props, metadata.atom));
      propertySupport.invalidatingChangePostHook();
      return true;
    };

    model.getAtomProperties = function(i) {
      var atoms = modelState.atoms,
          atomMetaData = metadata.atom,
          props = {},
          propName;

      for (propName in atomMetaData) {
        if (atomMetaData.hasOwnProperty(propName) && atoms[propName]) {
          props[propName] = atoms[propName][i];
        }
      }
      return translateFromMD2DUnits(props, atomMetaData);
    };

    model.getRadialBondsForAtom = function(i) {
      return engine.getRadialBondsForAtom(i);
    };

    model.getBondedAtoms = function(i) {
      return engine.getBondedAtoms(i);
    };

    model.getAngularBondsForAtom = function(i) {
      return engine.getAngularBondsForAtom(i);
    };

    model.getMoleculeAtoms = function(i) {
      return engine.getMoleculeAtoms(i);
    };

    model.setElementProperties = function(i, props) {
      // Validate properties.
      props = validator.validate(metadata.element, props);
      if (aminoacidsHelper.isAminoAcid(i)) {
        throw new Error("Elements: elements with ID " + i + " cannot be edited, as they define amino acids.");
      }
      propertySupport.invalidatingChangePreHook();
      engine.setElementProperties(i, translateToMD2DUnits(props, metadata.element));
      propertySupport.invalidatingChangePostHook();
    };

    model.getElementProperties = function(i) {
      var elementMetaData = metadata.element,
          props = {},
          propName;
      for (propName in elementMetaData) {
        if (elementMetaData.hasOwnProperty(propName)) {
          props[propName] = elements[propName][i];
        }
      }
      return translateFromMD2DUnits(props, elementMetaData);
    };

    model.setObstacleProperties = function(i, props) {
      // Validate properties.
      props = validator.validate(metadata.obstacle, props);
      propertySupport.invalidatingChangePreHook();
      engine.setObstacleProperties(i, translateToMD2DUnits(props, metadata.obstacle));
      propertySupport.invalidatingChangePostHook();
    };

    model.getObstacleProperties = function(i) {
      var obstacleMetaData = metadata.obstacle,
          props = {},
          propName;
      for (propName in obstacleMetaData) {
        if (obstacleMetaData.hasOwnProperty(propName)) {
          props[propName] = obstacles[propName][i];
        }
      }
      return translateFromMD2DUnits(props, obstacleMetaData);
    };

    model.setShapeProperties = function(i, props) {
      // Validate properties.
      props = validator.validate(metadata.shape, props);
      propertySupport.invalidatingChangePreHook();
      engine.setShapeProperties(i, translateToMD2DUnits(props, metadata.shape));
      propertySupport.invalidatingChangePostHook();
    };

    model.getShapeProperties = function(i) {
      var shapeMetaData = metadata.shape,
          props = {},
          propName;
      for (propName in shapeMetaData) {
        if (shapeMetaData.hasOwnProperty(propName)) {
          props[propName] = shapes[propName][i];
        }
      }
      return translateFromMD2DUnits(props, shapeMetaData);
    };

    model.setLineProperties = function(i, props) {
      // Validate properties.
      props = validator.validate(metadata.line, props);
      propertySupport.invalidatingChangePreHook();
      engine.setLineProperties(i, translateToMD2DUnits(props, metadata.line));
      propertySupport.invalidatingChangePostHook();
    };

    model.getLineProperties = function(i) {
      var lineMetaData = metadata.line,
          props = {},
          propName;
      for (propName in lineMetaData) {
        if (lineMetaData.hasOwnProperty(propName)) {
          props[propName] = lines[propName][i];
        }
      }
      return translateFromMD2DUnits(props, lineMetaData);
    };

    model.setElectricFieldProperties = function(i, props) {
      // Validate properties.
      props = validator.validate(metadata.electricField, props);
      propertySupport.invalidatingChangePreHook();
      engine.setElectricFieldProperties(i, translateToMD2DUnits(props, metadata.electricField));
      propertySupport.invalidatingChangePostHook();
      dispatch.changeElectricField();
    };

    model.getElectricFieldProperties = function(i) {
      var elFieldMetaData = metadata.electricField,
          props = {},
          propName;
      for (propName in elFieldMetaData) {
        if (elFieldMetaData.hasOwnProperty(propName)) {
          props[propName] = electricFields[propName][i];
        }
      }
      return translateFromMD2DUnits(props, elFieldMetaData);
    };

    model.setRadialBondProperties = function(i, props) {
      // Validate properties.
      props = validator.validate(metadata.radialBond, props);
      propertySupport.invalidatingChangePreHook();
      engine.setRadialBondProperties(i, translateToMD2DUnits(props, metadata.radialBond));
      propertySupport.invalidatingChangePostHook();
    };

    model.getRadialBondProperties = function(i) {
      var radialBondMetaData = metadata.radialBond,
          props = {},
          propName;
      for (propName in radialBondMetaData) {
        if (radialBondMetaData.hasOwnProperty(propName)) {
          props[propName] = radialBonds[propName][i];
        }
      }
      return translateFromMD2DUnits(props, radialBondMetaData);
    };

    model.setRestraintProperties = function(i, props) {
      // Validate properties.
      props = validator.validate(metadata.restraint, props);
      propertySupport.invalidatingChangePreHook();
      engine.setRestraintProperties(i, translateToMD2DUnits(props, metadata.restraint));
      propertySupport.invalidatingChangePostHook();
    };

    model.getRestraintProperties = function(i) {
      var restraintMetaData = metadata.restraint,
          props = {},
          propName;
      for (propName in restraintMetaData) {
        if (restraintMetaData.hasOwnProperty(propName)) {
          props[propName] = restraints[propName][i];
        }
      }
      return translateFromMD2DUnits(props, restraintMetaData);
    };

    model.setAngularBondProperties = function(i, props) {
      // Validate properties.
      props = validator.validate(metadata.angularBond, props);
      propertySupport.invalidatingChangePreHook();
      engine.setAngularBondProperties(i, translateToMD2DUnits(props, metadata.angularBond));
      propertySupport.invalidatingChangePostHook();
    };

    model.getAngularBondProperties = function(i) {
      var angularBondMetaData = metadata.angularBond,
          props = {},
          propName;
      for (propName in angularBondMetaData) {
        if (angularBondMetaData.hasOwnProperty(propName)) {
          props[propName] = angularBonds[propName][i];
        }
      }
      return translateFromMD2DUnits(props, angularBondMetaData);
    };

    model.setSolvent = function (solventName) {
      var solvent = new Solvent(solventName),
          props = {
            solventForceType: solvent.forceType,
            dielectricConstant: solvent.dielectricConstant,
            backgroundColor: solvent.color
          };
      model.set(props);
    };

    /** A "spring force" is used to pull atom `atomIndex` towards (x, y). We expect this to be used
       to drag atoms interactively using the mouse cursor (in which case (x,y) is the mouse cursor
       location.) In these cases, use the liveDragStart, liveDrag, and liveDragEnd methods instead
       of this one.

       The optional springConstant parameter (measured in eV/nm^2) is used to adjust the strength
       of the "spring" pulling the atom toward (x, y)

       @returns ID (index) of the spring force among all spring forces
    */
    model.addSpringForce = function(atomIndex, x, y, springConstant) {
      if (springConstant == null) springConstant = 500;

      if (unitsTranslation) {
        springConstant = unitsTranslation.translateToModelUnits(springConstant, 'stiffness');
      }
      return engine.addSpringForce(atomIndex, x, y, springConstant);
    };

    /**
      Update the (x, y) position of a spring force.
    */
    model.updateSpringForce = function(springForceIndex, x, y) {
      engine.updateSpringForce(springForceIndex, x, y);
    };

    /**
      Remove a spring force.
    */
    model.removeSpringForce = function(springForceIndex) {
      engine.removeSpringForce(springForceIndex);
    };

    model.addTextBox = function(props) {
      props = validator.validateCompleteness(metadata.textBox, props);
      model.get('textBoxes').push(props);
      dispatch.textBoxesChanged();
    };

    model.removeTextBox = function(i) {
      var text = model.get('textBoxes');
      if (i >=0 && i < text.length) {
        model.set('textBoxes', text.slice(0,i).concat(text.slice(i+1)));
        dispatch.textBoxesChanged();
      } else {
        throw new Error("Text box \"" + i + "\" does not exist, so it cannot be removed.");
      }
    };

    model.setTextBoxProperties = function(i, props) {
      var textBox = model.get('textBoxes')[i],
          prop;

      if (textBox) {
        props = validator.validate(metadata.textBox, props);
        for (prop in props) {
          if (props.hasOwnProperty(prop)) {
            textBox[prop] = props[prop];
          }
        }
        dispatch.textBoxesChanged();
      } else {
        throw new Error("Text box \"" + i + "\" does not exist, so it cannot have properties set.");
      }
    };

    model.setImageProperties = function(i, props) {
      var image = model.get('images')[i],
          prop;

      if (image) {
        props = validator.validate(metadata.image, props);
        for (prop in props) {
          if (props.hasOwnProperty(prop)) {
            image[prop] = props[prop];
          }
        }
        dispatch.imagesChanged();
      } else {
        throw new Error("Image \"" + i + "\" does not exist, so it cannot have properties set.");
      }
    };

    model.getTextBoxProperties = function(i) {
      return model.get('textBoxes')[i];
    };

    model.getImageProperties = function(i) {
      return model.get('images')[i];
    };

    /**
      Implements dragging of an atom in a running model, by creating a spring force that pulls the
      atom towards the mouse cursor position (x, y) and damping the resulting motion by temporarily
      adjusting the friction of the dragged atom.
    */
    model.liveDragStart = function(atomIndex, x, y) {
      if (liveDragSpringForceIndex != null) return;    // don't add a second liveDrag force

      if (x == null) x = modelState.atoms.x[atomIndex];
      if (y == null) y = modelState.atoms.y[atomIndex];

      liveDragSavedFriction = model.getAtomProperties(atomIndex).friction;

      // Use setAtomProperties so that we handle things correctly if a web worker is integrating
      // the model. (Here we follow the rule that we must assume that an integration might change
      // any property of an atom, and therefore cause changes to atom properties in the main thread
      // to be be lost. This is true even though common sense tells us that the friction property
      // won't change during an integration.)

      model.setAtomProperties(atomIndex, { friction: model.LIVE_DRAG_FRICTION });

      liveDragSpringForceIndex = model.addSpringForce(atomIndex, x, y, 500);
    };

    /**
      Updates the drag location after liveDragStart
    */
    model.liveDrag = function(x, y) {
      model.updateSpringForce(liveDragSpringForceIndex, x, y);
    };

    /**
      Cancels a live drag by removing the spring force that is pulling the atom, and restoring its
      original friction property.
    */
    model.liveDragEnd = function() {
      var atomIndex = engine.springForceAtomIndex(liveDragSpringForceIndex);

      model.setAtomProperties(atomIndex, { friction: liveDragSavedFriction });
      model.removeSpringForce(liveDragSpringForceIndex);
      liveDragSpringForceIndex = null;
    };

    /**
     * Returns number of frames per second.
     * @return {number} frames per second.
     */
    model.getFPS = function() {
      var s = 0,
          n = sampleTimes.length,
          i = -1;

      while (++i < n) {
        s += sampleTimes[i];
      }
      s /= n;
      return (s ? 1 / s * 1000 : 0);
    };

    /**
     * Returns "simulation progress rate".
     * It indicates how much of simulation time is calculated for
     * one second of real time.
     * @return {number} simulation progress rate.
     */
    model.getSimulationProgressRate = function() {
      return model.getFPS() * model.get('timeStep') * model.get('timeStepsPerTick');
    };

    model.get_elements = function() {
      return elements;
    };

    model.getAtoms = function() {
      return viewAtoms;
    };

    model.getRadialBonds = function() {
      return viewRadialBonds;
    };

    model.getElectricField = function() {
      return electricField;
    };

    model.getPhotons = function() {
      return viewPhotons;
    };

    // *really* need a way for the QD plugin to add these methods to modeler-layer
    model.turnOnLightSource = function() {
      // Note the current, very temporary, implementation ignores index and isn't an
      // accessor. But this is for prototyping.
      engine.callPluginAccessor('turnOnLightSource');
    };

    model.turnOffLightSource = function() {
      engine.callPluginAccessor('turnOffLightSource');
    };

    model.setLightSourceAngle = function(angle) {
      engine.callPluginAccessor('setLightSourceAngle', [angle]);
    };

    model.setLightSourceFrequency = function(freq) {
      engine.callPluginAccessor('setLightSourceFrequency', [freq]);
    };

    model.setLightSourcePeriod = function(period) {
      engine.callPluginAccessor('setLightSourcePeriod', [period]);
    };

    model.setLightSourceNumber = function(number) {
      engine.callPluginAccessor('setLightSourceNumber', [number]);
    };

    model.setBondEnergy = function(bondDescription, value) {
      engine.callPluginAccessor('setBondEnergy', [bondDescription, value]);
    };

    model.setValenceElectrons = function(element, value) {
      engine.callPluginAccessor('setValenceElectrons', [element, value]);
    };

    var _photonAbsorbedHandlersCount = 0;
    model.onPhotonAbsorbed = function(handler) {
      // Lab's DispatchSupport requires unique namespace for each different handler.
      engine.callPluginAccessor('on', ['photonAbsorbed.external-handler-' + _photonAbsorbedHandlersCount, handler]);
    };

    /**
      Returns the total number of atoms, or else the number of atoms matching some criterion.

      If the argument 'f' is present, it is called once for each atom, passing the atom as the
      argument to f. The number of atoms for which f evaluates to true is returned.

      Example

        model.getNumberOfAtoms(function(atom) { return atom.mass < 50; })

      returns the number of atoms having mass < 50
    */
    model.getNumberOfAtoms = function(f) {
      if (!f) {
        return viewAtoms.length;
      }
      return viewAtoms.reduce(function(total, atom) {
        return f(atom) ? total + 1 : total;
      }, 0);
    };

    model.get_obstacles = function() {
      return obstacles;
    };

    model.get_shapes = function() {
      return shapes;
    };

    model.get_lines = function() {
      return lines;
    };

    // FIXME. Should be an output property.
    model.getNumberOfElements = function () {
      return engine.getNumberOfElements();
    };

    // FIXME. Should be an output property.
    model.getNumberOfObstacles = function () {
      return engine.getNumberOfObstacles();
    };

    model.getNumberOfShapes = function () {
      return engine.getNumberOfShapes();
    };

    model.getNumberOfLines = function () {
      return engine.getNumberOfLines();
    };

    // FIXME. Should be an output property.
    model.getNumberOfRadialBonds = function () {
      return engine.getNumberOfRadialBonds();
    };

    // FIXME. Should be an output property.
    model.getNumberOfAngularBonds = function () {
      return engine.getNumberOfAngularBonds();
    };

    // FIXME. Should be an output property.
    model.getNumberOfSpringForces = function () {
      return engine.getNumberOfSpringForces();
    };

    // FIXME. Should be an output property.
    model.getNumberOfElectricFields = function () {
      return engine.getNumberOfElectricFields();
    };

    model.getNumberOfTextBoxes = function () {
      return  model.get('textBoxes').length;
    };

    model.get_restraints = function() {
      return restraints;
    };

    model.getPairwiseLJProperties = function() {
      return engine.pairwiseLJProperties;
    };

    model.geneticEngine = function() {
      return geneticEngine;
    };

    model.get_vdw_pairs = function() {
      return engine.getVdwPairsArray();
    };

    model.tickInPlace = function() {
      dispatch.tick();
      return model;
    };

    model.tick = function() {
      var timeStep = model.get('timeStep'),
          t, sampleTime;

      if (unitsTranslation) {
        timeStep = unitsTranslation.translateToModelUnits(timeStep, 'time');
      }

      if (!model.isStopped()) {
        t = performance.now();
        if (lastSampleTime) {
          sampleTime = t - lastSampleTime;
          lastSampleTime = t;
          sampleTimes.push(sampleTime);
          sampleTimes.splice(0, sampleTimes.length - 64);

          // Process all transitions which are in progress
          // and remove finished.
          processTransitions(sampleTime);
        } else {
          lastSampleTime = t;
        }
      }

      performance.enterScope("engine");
      // timeStepsPerTick is defined in Classic MW as the number of timesteps per view update.
      // However, in MD2D we prefer the more physical notion of integrating for a particular
      // length of time.
      engine.integrate(model.get('timeStepsPerTick') * timeStep, timeStep);
      performance.leaveScope("engine");

      readModelState();
      model.updateAllOutputProperties();
      tickHistory.push();

      newStep = true;

      // TODO: we should just dispatch "radialBondsChanged" event, as there is no code interested
      // whether we really added or removed radial bond.
      if (engine.radialBondsChanged) dispatch.addRadialBond();

      dispatch.tick();
    };

    model.minimizeEnergy = function () {
      propertySupport.invalidatingChangePreHook();
      engine.minimizeEnergy();
      propertySupport.invalidatingChangePostHook();
      // Positions of atoms could change, so
      // dispatch tick event.
      dispatch.tick();
      return model;
    };

    model.dimensions = function() {
      return engine.getDimensions();
    };

    model.format = function(property, opts) {
      opts = opts || {};

      var desc = model.getPropertyDescription(property);
      if (desc) {
        return desc.format(model.get(property), opts);
      }
      return d3.format(opts.format || 'g')(model.get(property));
    };

    /**
      Return a unitDefinition in the current unitScheme for a quantity
      such as 'length', 'mass', etc.
    */
    model.getUnitDefinition = function(name) {
      return unitsDefinition.units[name];
    };

    /**
     * Returns atom transition object. It can be used to smoothly change
     * atom properties over specified time. It's similar to D3 transitions.
     *
     * Atom transition object provides following methods:
     *  id(id)          - sets ID of the atom (required!).
     *  duration(d)     - sets duration in ms (required!).
     *  prop(name, val) - sets property name and its final value (required!).
     *  delay(d)        - sets delay in ms (default is 0).
     *  ease(name)      - sets easing function (default is "cubic-in-out").
     *                    Please see:
     *                    https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease
     *
     * e.g.
     *  atomTransition().id(0).duration(1000).ease("linear").prop("x", 10);
     *
     * This will change "x" property of the atom with ID=0
     * to value 10 over 1000ms using linear easing function.
     *
     * @return {AtomTransition} AtomTransition instance.
     */
    model.atomTransition = function () {
      var t = new AtomTransition(model);
      transitions.push(t);
      return t;
    };

    /**
     * Cancels all transitions which are currently in progress.
     */
    model.cancelTransitions = function () {
      transitions.length = 0;
    };

    /**
      Call before running a function that would otherwise trigger a number
      of invalidatingChangePre/PostHooks, which would slow down the model when
      each change causes a recalculation. This can be used whenever you can
      safely assume that all actions executed between startBatch and endBatch
      will not depend on triggered property changes.

      endBatch() *must* be called after the actions are complete, or output
      properties will no longer be updated.
      */
    model.startBatch = function() {
      propertySupport.startBatch();
      // Suppress events dispatching. They will be dispatched during
      // .endBatch() execution.
      dispatch.startBatch();
    };

    model.endBatch = function() {
      propertySupport.endBatch();
      // All events will be dispatched now (but just once per event type).
      dispatch.endBatch();
    };

    /**
     * Executes function between .startBatch() and .endBatch() calls.
     * @param  {Function} action function that should be executed.
     */
    model.batch = function(action) {
      model.startBatch();
      action();
      model.endBatch();
    };

    // Convert array of hashes to a hash of arrays
    // TODO. Move to a new utils module, share with mml parser
    function unroll(array) {
      var ret = {};

      if (array.length === 0) {
        return {};
      }

      Object.keys(array[0]).forEach(function(key) {
        ret[key] = [];
      });

      array.forEach(function(object) {
        Object.keys(object).forEach(function(key) {
          ret[key].push(object[key]);
        });
      });
      return ret;
    }

    function serializeQuantumDynamics() {
      var photons = model.getPhotons(),
          data = {
            photons: serialize(metadata.photon, unroll(photons), photons.length),
            elementEnergyLevels: engine.callPluginAccessor('getElementEnergyLevels'),
            radiationlessEmissionProbability: engine.callPluginAccessor('getRadiationlessEmissionProbability'),
            lightSource: engine.callPluginAccessor('getLightSource')
          };

      if (!data.lightSource) delete data.lightSource;

      return data;
    }

    function serializeChemicalReactions() {
      return {};
    }

    model.serialize = function() {
      var propCopy = {},
          ljProps, i, len,
          rawProperties = propertySupport.rawValues,

          removeAtomsArrayIfDefault = function(name, defaultVal) {
            if (propCopy.atoms[name].every(function(i) {
              return i === defaultVal;
            })) {
              delete propCopy.atoms[name];
            }
          };

      propCopy = serialize(metadata.mainProperties, rawProperties);
      propCopy.viewOptions = serialize(metadata.viewOptions, rawProperties);
      propCopy.atoms = serialize(metadata.atom, modelState.atoms, engine.getNumberOfAtoms());

      if (engine.getNumberOfRadialBonds()) {
        propCopy.radialBonds = serialize(metadata.radialBond, radialBonds, engine.getNumberOfRadialBonds());
      }
      if (engine.getNumberOfAngularBonds()) {
        propCopy.angularBonds = serialize(metadata.angularBond, angularBonds, engine.getNumberOfAngularBonds());
      }
      if (engine.getNumberOfObstacles()) {
        propCopy.obstacles = serialize(metadata.obstacle, obstacles, engine.getNumberOfObstacles());
        for (i = 0, len = propCopy.obstacles.x.length; i < len; i++) {
          // Silly, but allows to pass current serialization tests.
          // FIXME: try to create more flexible tests for serialization.
          propCopy.obstacles.westProbe[i] = Boolean(propCopy.obstacles.westProbe[i]);
          propCopy.obstacles.northProbe[i] = Boolean(propCopy.obstacles.northProbe[i]);
          propCopy.obstacles.eastProbe[i] = Boolean(propCopy.obstacles.eastProbe[i]);
          propCopy.obstacles.southProbe[i] = Boolean(propCopy.obstacles.southProbe[i]);
        }
      }
      if (engine.getNumberOfShapes()) {
        propCopy.shapes = serialize(metadata.shape, shapes, engine.getNumberOfShapes());
      }
      if (engine.getNumberOfLines()) {
        propCopy.lines = serialize(metadata.line, lines, engine.getNumberOfLines());
      }
      if (engine.getNumberOfElectricFields()) {
        propCopy.electricFields = serialize(metadata.electricField, electricFields, engine.getNumberOfElectricFields());
      }
      if (engine.getNumberOfRestraints()) {
        propCopy.restraints = serialize(metadata.restraint, restraints, engine.getNumberOfRestraints());
      }

      // FIXME: for now Amino Acid elements are *not* editable and should not be serialized
      // -- only copy first five elements
      propCopy.elements = serialize(metadata.element, elements, 5);

      // The same situation for Custom LJ Properties. Do not serialize properties for amino acids.
      propCopy.pairwiseLJProperties = [];
      ljProps = engine.pairwiseLJProperties.serialize();
      for (i = 0, len = ljProps.length; i < len; i++) {
        if (ljProps[i].element1 <= 5 && ljProps[i].element2 <= 5) {
          propCopy.pairwiseLJProperties.push(ljProps[i]);
        }
      }

      // Do the weird post processing of the JSON, which is also done by MML parser.
      // Remove targetTemperature when heat-bath is disabled.
      if (propCopy.temperatureControl === false) {
        delete propCopy.targetTemperature;
      }
      // Remove atomTraceId when atom tracing is disabled.
      if (propCopy.viewOptions.showAtomTrace === false) {
        delete propCopy.viewOptions.atomTraceId;
      }
      if (propCopy.modelSampleRate === "default") {
        delete propCopy.modelSampleRate;
      }

      // TODO. Should be able to ask plugins to serialize their data.
      if (model.properties.useQuantumDynamics) {
        propCopy.quantumDynamics = serializeQuantumDynamics();
      }

      if (model.properties.useChemicalReactions) {
        propCopy.chemicalReactions = serializeChemicalReactions();
      } else {
        delete propCopy.atoms.radical;
      }

      ['marked', 'visible', 'draggable', 'draggableWhenStopped'].forEach(function(prop) {
        removeAtomsArrayIfDefault(prop, metadata.atom[prop].defaultValue);
      });

      return propCopy;
    };

    // ------------------------------
    // finish setting up the model
    // ------------------------------

    // Friction parameter temporarily applied to the live-dragged atom.
    model.LIVE_DRAG_FRICTION = 10;

    // ------------------------------
    // Process initialProperties before setting properties on the model
    // ------------------------------

    // Ensure that model, which includes DNA (=> so DNA animation too) has
    // correct, constant dimensions. This is very significant, as if model
    // dimensions are too big or too small, DNA elements can be unreadable. It
    // also ensures that aspect ratio of the model is reasonable for
    // animation.
    // TODO: move this to better place.
    if (initialProperties.DNA) {
      // Overwrite width and height options.
      initialProperties.width = 200;
      initialProperties.height = 5;
      // View options are optional, make sure that they are defined.
      initialProperties.viewOptions = initialProperties.viewOptions || {};
      initialProperties.viewOptions.viewPortX = 0;
      initialProperties.viewOptions.viewPortY = 0;
      initialProperties.viewOptions.viewPortWidth = 5;
      initialProperties.viewOptions.viewPortHeight = 3;
    }

    (function () {
      if (!initialProperties.viewOptions) {
        return;
      }

      // Temporal workaround to provide text boxes and images validation.
      // Note that text boxes and images are handled completely different from other objects
      // like atoms or obstacles. There is much of inconsistency and probably
      // it should be refactored anyway.
      var textBoxes = initialProperties.viewOptions.textBoxes || [],
          images = initialProperties.viewOptions.images || [],
          i, len;

      for (i = 0, len = textBoxes.length; i < len; i++) {
        textBoxes[i] = validator.validateCompleteness(metadata.textBox, textBoxes[i]);
      }
      for (i = 0, len = images.length; i < len; i++) {
        images[i] = validator.validateCompleteness(metadata.image, images[i]);
      }
    }());

    // TODO. Implement a pattern whereby the pluginController lets each plugins examine the initial
    // properties and extract the relevant plugin properties. *However*, don't do it in a way that
    // requires changing the model JSON schema when functionality is moved out of the main engine
    // and into a plugin, or vice-versa.
    pluginProperties = {
      quantumDynamics: initialProperties.quantumDynamics,
      chemicalReactions: initialProperties.chemicalReactions
    };

    // TODO: Elements are stored and treated different from other objects. This was enforced by
    // createNewAtoms() method which has been removed. Change also editableElements handling.
    editableElements = initialProperties.elements;
    // Create editable elements.
    model.createElements(editableElements);
    // Create elements which specify amino acids also.
    createAminoAcids();

    // This will extend model API to support standard Lab model features. We
    // have to do it know, as this will also set initial properties, so the
    // engine has to be already defined (see custom setters).
    labModelerMixin.mixInto(model);

    // Initialize minX, minY, maxX, maxY from model width and height if they are undefined.
    initializeDimensions(model.properties);

    model.on("stop.last-sample-time-reset", function() {
      // This has to be done, as otherwise if user stops and then starts the
      // model, there will be an incorrect sample time reported (equal to time
      // period between starting and stopping the model).
      lastSampleTime = null;
    });
    propertySupport.on("afterInvalidatingChange.read-model-state", readModelState);
    propertySupport.on("afterInvalidatingChangeSequence.tick-history", function () {
      if (tickHistory) tickHistory.invalidateFollowingState();
      dispatch.invalidation();
    });

    // Setup MD2D engine object.
    initializeEngine(model.properties, pluginProperties);

    // Setup genetic engine.
    geneticEngine = new GeneticEngine(model);

    // Finally, if provided, set up the model objects (elements, atoms, bonds, obstacles and the rest).
    // However if these are not provided, client code can create atoms, etc piecemeal.

    // Trigger setter of polarAAEpsilon again when engine is initialized and
    // amino acids crated.
    // TODO: initialize engine before set_properties calls, so properties
    // will be injected to engine automatically.
    model.set({polarAAEpsilon: model.get('polarAAEpsilon')});

    if (initialProperties.atoms)          model.createAtoms(initialProperties.atoms);
    if (initialProperties.radialBonds)    model.createRadialBonds(initialProperties.radialBonds);
    if (initialProperties.angularBonds)   model.createAngularBonds(initialProperties.angularBonds);
    if (initialProperties.restraints)     model.createRestraints(initialProperties.restraints);
    if (initialProperties.obstacles)      model.createObstacles(initialProperties.obstacles);
    if (initialProperties.shapes)         model.createShapes(initialProperties.shapes);
    if (initialProperties.lines)          model.createLines(initialProperties.lines);
    if (initialProperties.electricFields) model.createElectricFields(initialProperties.electricFields);
    // Basically, this #deserialize method is more or less similar to other #create... methods used
    // above. However, this is the first step to delegate some functionality from modeler to smaller classes.
    if (initialProperties.pairwiseLJProperties)
      engine.pairwiseLJProperties.deserialize(initialProperties.pairwiseLJProperties);

    // Initialize tick history.
    tickHistory = new TickHistory({
      getProperties: function() {
        return propertySupport.historyStateRawValues;
      },
      restoreProperties: propertySupport.setRawValues,
      state: engine.getState()
    }, model, defaultMaxTickHistory);

    // Since we can't provide tickHistory to the mixed-in methods at the time we create
    // labModelerMixin (see below comment), provide it now.
    labModelerMixin.tickHistory = tickHistory;

    // FIXME: ugly workaround - mixin OutputSupport again, this time providing
    // tickHistory, so filtered outputs will use it. We couldn't pass
    // tickHistory directly to LabModelerMixin, as tickHistory depends on
    // propertySupport ...returned by LabModelrMixin. It has to be cleaned up.
    outputSupport = new OutputSupport({
      propertySupport: propertySupport,
      unitsDefinition: unitsDefinition,
      tickHistory: tickHistory
    });
    outputSupport.mixInto(model);

    newStep = true;

    // Define some default output properties.
    model.defineOutput('time', {
      label: "Time",
      unitType: 'time',
      format: 'f'
    }, function() {
      // Output getters are expected to return values in translated units, since authored outputs
      // can only read values already in translated units to start with.
      var value = modelState.time;
      if (unitsTranslation) {
        value = unitsTranslation.translateFromModelUnits(value, 'time');
      }
      return value;
    });

    model.defineOutput('timePerTick', {
      label: "Model time per tick",
      unitType: 'time',
      format: 'f'
    }, function() {
      return model.get('timeStep') * model.get('timeStepsPerTick');
    });

    (function() {
      var displayTimeUnits;
      var displayTimePropertyDescription;

      // Allow units definition to declare a "Display time"; specifically, let MD2D units definition
      // define a "displayValue" section in the time unit that returns ps instead of fs.

      if (unitsDefinition.units.time.displayValue) {
        displayTimeUnits = unitsDefinition.units.time.displayValue;
      } else {
        displayTimeUnits = _.extend({}, unitsDefinition.units.time);
        displayTimeUnits.unitsPerBaseUnit = 1;
      }

      displayTimePropertyDescription = new PropertyDescription(displayTimeUnits, {
        unitName:         displayTimeUnits.name,
        unitPluralName:   displayTimeUnits.pluralName,
        unitAbbreviation: displayTimeUnits.symbol,
        format: '.1f'
      });

      model.defineOutput('displayTime', {
        label: "Time",
        unitName:         displayTimeUnits.name,
        unitPluralName:   displayTimeUnits.pluralName,
        unitAbbreviation: displayTimeUnits.symbol,
        format: '.1f'
      }, function() {
        return model.get('time') * displayTimeUnits.unitsPerBaseUnit;
      });

      model.defineOutput('displayTimePerTick', {
        label: "Model time per tick",
        unitName:         displayTimeUnits.name,
        unitPluralName:   displayTimeUnits.pluralName,
        unitAbbreviation: displayTimeUnits.symbol,
        format: '.3f'
      }, function() {
        return model.get('timePerTick') * displayTimeUnits.unitsPerBaseUnit;
      });

      model.formatTime = function(time) {
        return  displayTimePropertyDescription.format(time * displayTimeUnits.unitsPerBaseUnit);
      };
    }());

    model.defineOutput('tickCounter', {
      label: "Tick Counter",
      unitType: '',
      format: '4g'
    }, function() {
      return tickHistory.get('counter');
    });

    model.defineOutput('newStep', {
      label: "New Step",
      unitType: '',
      format: ''
    }, function() {
      return newStep;
    });

    model.defineOutput('kineticEnergy', {
      label: "Kinetic Energy",
      unitType: 'energy',
      format: '.4g'
    }, function() {
      var value = modelState.KE;
      if (unitsTranslation) {
        value = unitsTranslation.translateFromModelUnits(value, 'energy');
      }
      return value;
    });

    model.defineOutput('potentialEnergy', {
      label: "Potential Energy",
      unitType: 'energy',
      format: '.4g'
    }, function() {
      var value = modelState.PE;
      if (unitsTranslation) {
        value = unitsTranslation.translateFromModelUnits(value, 'energy');
      }
      return value;
    });

    model.defineOutput('totalEnergy', {
      label: "Total Energy",
      unitType: 'energy',
      format: '.4g'
    }, function() {
      var value = modelState.KE + modelState.PE;
      if (unitsTranslation) {
        value = unitsTranslation.translateFromModelUnits(value, 'energy');
      }
      return value;
    });

    model.defineOutput('temperature', {
      label: "Temperature",
      unitType: 'temperature',
      format: 'f'
    }, function() {
      var value = modelState.temperature;
      if (unitsTranslation) {
        value = unitsTranslation.translateFromModelUnits(value, 'temperature');
      }
      return value;
    });

    // FIXME. More yuck: We still need a pattern for recompute model properties which don't depend
    // on physics (and which therefore can be recomputed without invalidating and recomputing all
    // the physics based properties) while still making them (1) observable and (2) read-only.

    // used to triggers recomputation of isPlayable property based on isStopped and isReady:
    model.on('play.model', recomputeProperties);
    model.on('stop.model', recomputeProperties);

    function recomputeProperties() {
      propertySupport.invalidatingChangePreHook();
      propertySupport.invalidatingChangePostHook();
    }

    model.defineOutput('isPlayable', {
      label: "Playable"
    }, function() {
      // FIXME: isStopped predates the use of ES5 getters, therefore it must be invoked
      return model.isReady && model.isStopped();
    });

    model.defineOutput('hasPlayed', {
      label: "has Played"
    }, function() {
      return model.hasPlayed;
    });

    model.defineOutput('isStopped', {
      label: "Stopped?"
    }, function() {
      return model.isStopped();
    });

    readModelState();
    model.updateAllOutputProperties();

    if (!initializationOptions.waitForSetup) {
      model.ready();
    }

    model.performanceOptimizer = new PerformanceOptimizer(model);

    model.namespace = namespace;

    return model;
  };
});

/*global define*/
/**
 * Views can require this function to get next available tab index.
 */
define('common/views/tab-index',[],function () {
  var tabIndex = 0;

  return function getNextTabIndex() {
    return tabIndex++;
  };
});

/*global define, $ */

define('common/feature-tests',[],function () {

  // Cache results of feature tests, as e.g. two different modules can ask about the same thing.
  var CACHE = {};

  return {
    /**
     * Returns true if CSS pointer-events: none style can be used to make an element transparent
     * for mouse events and document.elementFromPoint() function.
     */
    get cssPointerEvents() {
      if (CACHE.cssPointerEvents === undefined) {
        CACHE.cssPointerEvents = (function () {
          var $div1, $div2, $svg, result;
          // Using "fixed" positioning we can avoid thinking about scrollTop / scrollLeft values,
          // test elements will be always inside the current viewport.
          $div1 = $("<div class='supported'>").css({
            "position": "fixed",
            "left": 0,
            "top": 0,
            "width": "10px",
            "height": "10px"
          }).appendTo("body");
          $div2 = $("<div class='unsupported'>").css({
            "position": "fixed",
            "left": 0,
            "top": 0,
            "width": "10px",
            "height": "10px",
            "pointer-events": "none" // !!!
          }).appendTo("body");
          $svg = $("<svg class='unsupported'>").css({
            "position": "fixed",
            "left": 0,
            "top": 0,
            "width": "10px",
            "height": "10px",
            "pointer-events": "none" // !!!
          }).appendTo("body");

          result = $(document.elementFromPoint(5, 5)).attr("class");

          $svg.remove();
          $div2.remove();
          $div1.remove();

          switch(result) {
            case "supported":
              return true;
            case "unsupported":
              return false;
            default:
              console.warn("CSS pointer-events feature test failed");
              return false;
          }
        }());
      }
      return CACHE.cssPointerEvents;
    }
  };
});

/*global define: false */

/**
  Forward mouse events to the view's stack of overlaid visual layers to simulate the "hit
  testing" that would occur natively if the visual layers were all implemented as descendants of
  a single, common svg element.

  In that scenario, native hit-testing is used: a mouse event at any point is forwarded to the
  topmost visible element at that point; the layers above it are effectively transparent to the
  event. This is subject to control by the 'pointer-events' property. See
  http://www.w3.org/TR/SVG/interact.html#pointer-processing

  However, in order to allow some layers to be implemented as <canvas> elements (or perhaps
  <divs> or rootmost <svg> elements, to which hardware-accelerated css transforms apply), and to
  allow those layers to be above some layers and below others, our layers cannot in fact be
  children of a common svg element.

  This means that the topmost layer always becomes the target of any mouseevent on the view,
  regardless of whether it has an element at that point, and it hides the event from the layers
  below.

  What we need, therefore, is to simulate svg's hit testing. This can be achieved by listening
  to events that bubble to the topmost layer and successively hiding the layers below, applying
  document.elementFromPoint to determine whether there is an element within that layer at that
  event's coordinates. (Where the layer in question is a <canvas> with its own hit testing
  implemented in Javascript, we must defer to the layer's own hit-testing algorithm instead
  of using elementFromPoint.)

  CSS pointer-events (as distinct from SVG pointer events) cannot completely capture the
  semantics we need because they simply turn pointer "transparency" on or off rather than
  responding to the presence or absence of an element inside the layer at the event coordinates;
  and besides they do not work in IE9 or IE10.

  Note that to the extent we do use CSS pointer-events, document.elementFromPoint correctly
  respects the "none" value.

  What this means for event handling:

    * Mousedown, mouseup, and any custom mouse events such as those created by the jQuery
      ContextMenu plugin, are captured by a click shield element and prevented from bubbling.
      However, a clone event is dispatched to the element that passed the hit test and allowed
      to bubble to the window.

    * Click events on the click shield are captured and canceled because they are inherently not
      meaningful--the browser cannot tell if the mouseup and mousedown targets are "really" the
      same so it dispatches click events anytime a mousedown and mouseup occur on the view.
      Instead, if the mousedown and mouseup occur on the same element or sprite (e.g., same
      atom) we dispatch a synthetic click event targeted at the element. In the case that the
      mouseup occurs on a sprite in the canvas layer, the click event is only emitted if the
      same sprite was under the previous mousedown. Additionally, the target of a click on a
      sprite is the canvas layer itself since to DOM gives us no way to be any more specific.
      (Notice this means the same canvas can successfully hit test a mousedown followed by a
      mouseup, but still not cause a click to be issued, if the mousedown and mouseup were over
      two different sprites.)

    * Canvas elements should not listen for click events. They are responsible instead for
      performing a click action, if appropriate, when they receive a mouseup, and notifying the
      parent whether a DOM click event should be issued. (This prevents them from having to
      hit-test the same coordinates twice, once for the mouseup and then once for a click.)

    * Single-touch containing touch events will be captured and canceled, and synthetic mouse
      events corresponding to the touches will be issued and routed through the above-discussed
      hit test exactly as if they were mouse events. This is a reasonable choice because we do
      not use any multitouch gestures and must retain compatibility with desktop browsers.

    * Note that mouseover/mouseout/mousenter/mouseleave events are not handled in any way!
*/
define('common/views/hit-testing-helper',['require','common/feature-tests'],function (require) {
  // Dependencies.
  var featureTests = require('common/feature-tests'),

      EVENT_TYPES = ['mousedown', 'mouseup', 'contextmenu'],

  // Keep track of window listeners, so helper can cleanup previous listeners. Note that it
  // means that only one event translation per window is allowed for now!
      windowListeners = [];

  function addWindowEventListener(type, listener, useCapture) {
    window.addEventListener(type, listener, useCapture);

    windowListeners.push({
      type: type,
      func: listener,
      capture: useCapture
    });
  }

  function cleanupWindowListeners() {
    var l;
    while (windowListeners.length > 0) {
      l = windowListeners.pop();
      window.removeEventListener(l.type, l.func, l.capture);
    }
  }

  function noop() {}

  /**
   * @param {Element} foregroundNode the top-most layer.
   */
  return function HitTestingHelper(foregroundNode) {
    var api;

    // A list of all outermost svg/canvas/div containers which may have clickable or touchable child
    // elements, ordered from topmost to bottom-most. Because the layers are siblings, not
    // ancestors, the upper layers prevent mouse and touch events from reaching the lower layers
    // even when no element within the upper layers is actually being clicked/touched.
    var layersToHitTest;

    // We need to hide HTML layers from mouse events. It can be achieved by setting
    // "pointer-events" style to "none", however it isn't supported by all browsers
    // (e.g. IE9, IE10, Safari 5). The fallback method is to set layer's visibility to "hidden".
    var propName    = featureTests.cssPointerEvents ? "pointerEvents" : "visibility";
    var propHidden  = featureTests.cssPointerEvents ? "none" : "hidden";
    var propBackup;

    var mousedownTarget;
    var targetForCreatedClick;
    var defaultPreventedFlag;
    var cancelClickFlag;

    // Return a cloned version of 'e' having 'target' as its target property; cancel the original
    // event.
    function retargetMouseEvent(e, target) {
      var clonedEvent = document.createEvent("MouseEvent");
      clonedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
      clonedEvent.target = target;
      clonedEvent.forwardedTouchEvent = e.forwardedTouchEvent;
      // IE 9+ creates events with pageX and pageY set to 0.
      // Trying to modify the properties throws an error,
      // so we define getters to return the correct values.
      // Solution based on the approach used in: https://github.com/jquery/jquery-simulate
      if (clonedEvent.pageX === 0 && clonedEvent.pageY === 0 && Object.defineProperty) {
        var doc = document.documentElement;
        var body = document.body;
        var clientX = e.clientX;
        var clientY = e.clientY;

        Object.defineProperty(clonedEvent, "pageX", {
          get: function() {
            return clientX +
              (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
              (doc && doc.clientLeft || body && body.clientLeft || 0);
          }
        });
        Object.defineProperty(clonedEvent, "pageY", {
          get: function() {
            return clientY +
              (doc && doc.scrollTop || body && body.scrollTop || 0) -
              (doc && doc.clientTop || body && body.clientTop || 0);
          }
        });
      }
      return clonedEvent;
    }

    // Create a click event from a mouse event (presumably mouseup). Leaves original event as-is.
    function createClick(e, target) {
      // TODO. Does copying the properties adequately capture all the semantics of the click event?
      var clonedEvent = document.createEvent("MouseEvent");
      clonedEvent.initMouseEvent('click', true, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
      clonedEvent.target = target;
      return clonedEvent;
    }

    // Hide layer from mouse events using visibility or pointer-events styles.
    function hideLayer(i) {
      var layer = layersToHitTest[i];
      propBackup[i] = layer.style[propName];
      layer.style[propName] = propHidden;
    }

    // Restore original visibility or pointer-events styles of layers n to 0, inclusive.
    function unhideLayers(n) {
      for (var i = n; i >= 0; i--) {
        layersToHitTest[i].style[propName] = propBackup[i];
      }
    }

    function hitTest(e) {
      // Remember style rules of the layers we peel back
      propBackup = [];

      var layer;
      var target;
      var mouseEvent;
      var hitTestSucceeded;
      var isCanvasObjectClick;
      var layerBgColor;

      // Must be set, as we test it after calling hitTest()
      targetForCreatedClick = null;

      for (var i = 0, len = layersToHitTest.length; i < len; i++) {
        layer = layersToHitTest[i];

        if (i > 0) {
          hideLayer(i - 1);
        }

        if (layer.tagName.toLowerCase() === "canvas") {
          // For now we have to dispatch an event first, *then* see if the Canvas-based view
          // considered it a hit -- we stopPropagation and keep going if it does not report a hit.
          mouseEvent = retargetMouseEvent(e, layer);

          // Need to ask the Canvas-based view to perform custom hit-testing.
          // TODO: make this a static function rather than rebinding to closure each time.
          api.hitTestCallback = function(isHit) {
            hitTestSucceeded = isHit;
            if (!isHit) {
              mouseEvent.stopPropagation();
              mouseEvent.preventDefault();
            }
          };
          api.mouseupCallback = function(isClick) {
            isCanvasObjectClick = isClick;
          };

          layer.dispatchEvent(mouseEvent);

          // Restore safe noop functions. It ensures that even if client code calls one of them
          // unnecessarily, it won't have unwanted, side effects like .preventDefault() or
          // .stopPropagation() calls.
          api.hitTestCallback = noop;
          api.mouseupCallback = noop;

          if (isCanvasObjectClick) {
            // The canvas view itself won't listen to this click, but let the click bubble.
            targetForCreatedClick = layer;
          }

          if (hitTestSucceeded) {
            unhideLayers(i-1);
            defaultPreventedFlag = mouseEvent.defaultPrevented;
            return layer;
          }
        } else {
          // IE bug: without background color layer will be transparent for .elementFromPoint(),
          // underlying canvas (if any) will become a target. See:
          // https://www.pivotaltracker.com/story/show/58418116
          layerBgColor = layer.style.backgroundColor;
          layer.style.backgroundColor = "rgba(0,0,0,0)";

          // clientX and clientY report the event coords in CSS pixels relative to the viewport
          // (ie they aubtract the x, y the page is scrolled to). This is what elementFromPoint
          // requires in Chrome, Safari 5+, IE 8+, and Firefox 3+.
          // http://www.quirksmode.org/dom/tests/elementfrompoint.html
          // http://www.quirksmode.org/blog/archives/2010/06/new_webkit_test.html
          // http://www.quirksmode.org/mobile/tableViewport_desktop.html
          target = document.elementFromPoint(e.clientX, e.clientY);

          // Restore original background color.
          layer.style.backgroundColor = layerBgColor;

          // FIXME? Since we nominally allow target layers to be hidden or have pointer-events: none
          // we would have to replace this simplistic test. In the case that the layer is
          // transparent to events even before we hide it, target !== layer not because target is an
          // element in the layer that received the hit but because the target is below the layer.
          if (target !== layer) {
            unhideLayers(i-1);
            mouseEvent = retargetMouseEvent(e, target);
            target.dispatchEvent(mouseEvent);
            defaultPreventedFlag = mouseEvent.defaultPrevented;
            // There was an element in the layer at the event target. This hides the event from all
            // layers below, so we're done.
            return target;
          }
        }
      }
      // If no element is hit, make sure that all layer properties are restored.
      unhideLayers(layersToHitTest.length - 2); // -2 because the last layer is never hidden
    }

    // Translate any touch events on the foreground which have only a single touch point ("finger")
    // when started, to the corresponding mouse events. Does not attempt to initiate a cancel action
    // for touchcancel; just issues mouseup stops tracking the touch.
    function setupTouchEventTranslation(foregroundNode) {
      // Identifier of the touch point we are tracking; set to null when touch not in progress.
      var touchId = null;
      var touchStartX;
      var touchStartY;

      function createMouseEvent(touch, type) {
        var mouseEvent = document.createEvent("MouseEvent");
        mouseEvent.initMouseEvent(type, true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
        // This flag prevents FastClick from trying to pre-emptively cancel the event
        mouseEvent.forwardedTouchEvent = true;
        return mouseEvent;
      }

      // Detect whether the touch was moved >10 device pixels between start and end; if the touch
      // moved we may infer that the user performed a scroll.
      //
      // Contra @ppk (http://www.quirksmode.org/mobile/viewports2.html), screenX and screenY *are*
      // useful here because we're in an iframe. clientX/Y and pageX/Y are relative to our page and
      // visual viewport, respectively, *but those both move in rough synchrony with the touch* (and
      // we can't detect the scroll itself because the iframe's page and visual viewport offsets
      // don't change! The *outer* page changes but we may not be allowed to measure that.)
      // Fortunately, screenX and screenY are relative to the device, which lets us know if the
      // viewport moved "physically" (bonus: measurements in device pixels correspond to actual
      // physical distances in the user's world, and don't change with zoom).
      function didTouchMove(touch) {
        return Math.abs(touch.screenX - touchStartX) > 10 || Math.abs(touch.screenY - touchStartY) > 10;
      }

      // listener for touchstart
      function touchStarted(e) {
        var touch = e.changedTouches[0];

        if (e.touches.length > 1 || touch.target !== foregroundNode) {
          return;
        }

        cancelClickFlag = false;
        touchStartX = touch.screenX;
        touchStartY = touch.screenY;

        // Remember which touch point--later touch events may or may not include this touch point
        // but we have to listen to them all to make sure we update dragging state correctly.
        touchId = touch.identifier;
        foregroundNode.dispatchEvent(createMouseEvent(touch, 'mousedown'));

        if (defaultPreventedFlag) {
          e.preventDefault();
        }
        e.stopPropagation();
      }

      // Listener for touchmove, touchend, and touchcancel:
      function touchChanged(e) {

        if (touchId === null) {
          return;
        }

        // Don't translate touch events to mouse events when there are more than two fingers
        // on the screen. This lets us to support pinch-zoom even if user started gesture only
        // with one finger and added second later.
        if (e.type === "touchmove" && e.touches.length > 1) {
          // User added a second finger, perhaps he wants to do pinch-zoom or pan rather than
          // trigger "click" action at the end.
          cancelClickFlag = true;
          // In theory this isn't 100% correct, but... it ensures that no handler will be able
          // to call .preventDefault() on this event (e.q. D3 does that for touch events), so we
          // will always native browser behavior like panning or pinch-zoom. This is reasonable
          // assumption that 2-finger gestures are always used for navigation.
          e.stopPropagation();
          return;
        }

        var i;
        var len;
        var touch;
        var target;
        var mouseMoveEvent;

        for (i = 0, len = e.changedTouches.length; i < len; i++) {
          touch = e.changedTouches[i];

          if (touch.identifier !== touchId) {
            continue;
          }

          if (len === 1) {
            e.stopPropagation();
          }

          // touch's target will always be the element that received the touchstart. But since
          // we're creating a pretend mousemove, let its target be the target the browser would
          // report for an actual mousemove/mouseup (Remember that the--expensive--hitTest() is not
          // called for mousemove, though; drag handlers should and do listen for mousemove on the
          // window). Note that clientX can be off the document, so report window in that case!
          target = document.elementFromPoint(touch.clientX, touch.clientY) || window;

          if (e.type === 'touchmove') {
            if (!cancelClickFlag) {
              // Cancel "click" event when finger has moved (> 10px at the moment).
              cancelClickFlag = didTouchMove(touch);
            }
            mouseMoveEvent = createMouseEvent(touch, 'mousemove');
            target.dispatchEvent(mouseMoveEvent);
            // mousemove events can be captured and replaced by synthetic events (see .passMouseMove
            // method). In such case 'defaultPreventedFlag' will be automatically set to correct
            // value. However when event isn't captured and replaced by synthetic one, we have to
            // check .defaultPrevented property manually.
            if (!mouseMoveEvent.replacedBySynthetic) {
              defaultPreventedFlag = mouseMoveEvent.defaultPrevented;
            }
          } else if (e.type === 'touchend') {
            target.dispatchEvent(createMouseEvent(touch, 'mouseup'));
            touchId = null;
          } else if (e.type === 'touchcancel') {
            // Do not dispatch click event on touchcancel.
            cancelClickFlag = true;
            target.dispatchEvent(createMouseEvent(touch, 'mouseup'));
            touchId = null;
          }

          // .preventDefault() on touchend will prevent the browser from generating a events like
          // mousedown, mouseup and click. It's necessary as we already translated touchstart
          // to mousedown and touchend to mouseup. Our hit testing intentionally ignores
          // browser-generated click events anyway, and generates its own when appropriate.
          if (e.type === 'touchend' || defaultPreventedFlag) {
            e.preventDefault();
          }

          return;
        }
      }

      addWindowEventListener('touchstart', touchStarted, true);
      ['touchmove', 'touchend', 'touchcancel'].forEach(function (eventType) {
        addWindowEventListener(eventType, touchChanged, true);
      });

      // TODO implement cancel semantics for atom dragging?
      // see http://alxgbsn.co.uk/2011/12/23/different-ways-to-trigger-touchcancel-in-mobile-browsers/
      // until then we have to observe touchcancel to stop in-progress drags.
    }

    function init() {
      cleanupWindowListeners();

      layersToHitTest = [foregroundNode];

      EVENT_TYPES.forEach(function(eventType) {
        // Use a capturing handler on window so we can swallow the event
        addWindowEventListener(eventType, function(e) {
          var target;

          if (e.target !== foregroundNode) {
            return;
          }

          e.stopPropagation();
          // Chrome (and Safari?) bug: http://crbug.com/269917
          // If we call .preventDefault() on the original mousedown event, it will break handling of
          // events outside the iframe. It can cause issues like this one:
          // https://www.pivotaltracker.com/story/show/58446862
          if (e.type !== 'mousedown') {
            e.preventDefault();
          }

          target = hitTest(e);

          if (e.type === 'mousedown') {
            mousedownTarget = target;
          } else if (e.type === 'mouseup') {
            // Note that cancelClickFlag can be set to true if e.g. there was significant
            // touchmove between touchstart (mousedown) and touchend (mouseup). It means that
            // user was panning the viewport rather than trying to perform "click". Other case can
            // be when second finger was added between touchstart and touchend what can be used to
            // perform pinch-zoom rather than click.
            if (target && target === mousedownTarget &&
                target.tagName.toLowerCase() !== 'canvas' && !cancelClickFlag) {
              target.dispatchEvent(createClick(e), target);
            }
            if (targetForCreatedClick && !cancelClickFlag) {
              targetForCreatedClick.dispatchEvent(createClick(e), targetForCreatedClick);
            }
          }
        }, true);
      });

      // Completely swallow "click" events on the foregroundNode. The browser can't issue these
      // correctly; we have to issue them ourselves after a mouseup.
      addWindowEventListener('click', function(e) {
        if (e.target === foregroundNode) {
          e.stopPropagation();
          e.preventDefault();
        }
      }, true);

      setupTouchEventTranslation(foregroundNode);
    }

    api = {
      /**
       * Setups fast, simplified hit testing for mousemove events. 'mousemove' will be dispatched
       * only to the specified target element. Because of that we can avoid very expensive
       * .elementFromPoint() calls. Target will receive mouseover and mouseout events too.
       * @param  {Element} mmoveSource
       * @param  {Element} mmoveTarget
       */
      passMouseMove: function (mmoveSource, mmoveTarget) {
        var prevTarget;

        // Return a cloned version of 'e' but with a different 'type', 'target' and 'relatedTarget'.
        function createMouseEvent(e, type, target, relatedTarget) {
          var clonedEvent = document.createEvent("MouseEvent");
          clonedEvent.initMouseEvent(type, e.bubbles, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, relatedTarget || e.relatedTarget);
          clonedEvent.target = target;
          return clonedEvent;
        }

        addWindowEventListener('mousemove', function (e) {
          // Note that we have to check if 'e' is not a synthetic event that can be be dispatched by
          // this handler. Otherwise we will enter infinite loop.
          if (!e.synthetic) {
            var target, mouseEvent;
            if (e.target !== mmoveSource) {
              // Target is different from desired source, we can only dispatch mouseout and return.
              if (prevTarget === mmoveTarget) {
                mmoveTarget.dispatchEvent(createMouseEvent(e, "mouseout", mmoveTarget, e.target));
              }
              prevTarget = e.target;
              return;
            }
            target = mmoveTarget;
            if (target !== prevTarget) {
              mmoveTarget.dispatchEvent(createMouseEvent(e, "mouseover", mmoveTarget, prevTarget));
            }
            e.stopPropagation();
            e.preventDefault();
            e.replacedBySynthetic = true;
            mouseEvent = retargetMouseEvent(e, target);
            mouseEvent.synthetic = true; // !!!
            // We don't support more sophisticated mousemove event passing, e.g. based on the hit
            // test results (yet?), so just set noop callback.
            api.hitTestCallback = noop;
            target.dispatchEvent(mouseEvent);
            // Set the defaultPreventedFlag.
            defaultPreventedFlag = mouseEvent.defaultPrevented;
            prevTarget = target;
          }
        }, true);
      },

      /**
       * Registers a new layer that will receive mouse events.
       * @param  {Element} element
       */
      addLayer: function (element) {
        // Add new element *after* foreground element.
        layersToHitTest.splice(1, 0, element);
      },

      /**
       * Lets the code responsible for interaction inside canvas notify this helper whether a hit
       * really occurred inside canvas (e.g. some drawn element was hit or not). If this callback
       * isn't called (or is called with false argument), an event will be passed to the layers
       * below canvas.
       * @param {Boolean} isHit
       */
      hitTestCallback: noop,

      /**
       * Lets canvas notify this helper whether 'click' event should be dispatched.
       * @param {Boolean} isClick
       */
      mouseupCallback: noop
    };

    init();

    return api;
  };
});

/**
 * @license
 * Pixi.JS - v1.3.0
 * Copyright (c) 2012, Mat Groves
 * http://goodboydigital.com/
 *
 * Compiled: 2013-09-30
 *
 * Pixi.JS is licensed under the MIT License.
 * http://www.opensource.org/licenses/mit-license.php
 */
/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

(function(){

	var root = this;

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * @module PIXI
 */
var PIXI = PIXI || {};

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * The Point object represents a location in a two-dimensional coordinate system, where x represents the horizontal axis and y represents the vertical axis.
 *
 * @class Point
 * @constructor 
 * @param x {Number} position of the point
 * @param y {Number} position of the point
 */
PIXI.Point = function(x, y)
{
	/**
	 * @property x 
	 * @type Number
	 * @default 0
	 */
	this.x = x || 0;
	
	/**
	 * @property y
	 * @type Number
	 * @default 0
	 */
	this.y = y || 0;
}

/**
 * Creates a clone of this point
 *
 * @method clone
 * @return {Point} a copy of the point
 */
PIXI.Point.prototype.clone = function()
{
	return new PIXI.Point(this.x, this.y);
}

// constructor
PIXI.Point.prototype.constructor = PIXI.Point;


/**
 * @author Mat Groves http://matgroves.com/
 */

/**
 * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height.
 *
 * @class Rectangle
 * @constructor 
 * @param x {Number} The X coord of the upper-left corner of the rectangle
 * @param y {Number} The Y coord of the upper-left corner of the rectangle
 * @param width {Number} The overall wisth of this rectangle
 * @param height {Number} The overall height of this rectangle
 */
PIXI.Rectangle = function(x, y, width, height)
{
	/**
	 * @property x
	 * @type Number
	 * @default 0
	 */
	this.x = x || 0;
	
	/**
	 * @property y
	 * @type Number
	 * @default 0
	 */
	this.y = y || 0;
	
	/**
	 * @property width
	 * @type Number
	 * @default 0
	 */
	this.width = width || 0;
	
	/**
	 * @property height
	 * @type Number
	 * @default 0
	 */
	this.height = height || 0;
}

/**
 * Creates a clone of this Rectangle
 *
 * @method clone
 * @return {Rectangle} a copy of the rectangle
 */
PIXI.Rectangle.prototype.clone = function()
{
	return new PIXI.Rectangle(this.x, this.y, this.width, this.height);
}

/**
 * Checks if the x, and y coords passed to this function are contained within this Rectangle
 *
 * @method contains
 * @param x {Number} The X coord of the point to test
 * @param y {Number} The Y coord of the point to test
 * @return {Boolean} if the x/y coords are within this Rectangle
 */
PIXI.Rectangle.prototype.contains = function(x, y)
{
    if(this.width <= 0 || this.height <= 0)
        return false;

	var x1 = this.x;
	if(x >= x1 && x <= x1 + this.width)
	{
		var y1 = this.y;
		
		if(y >= y1 && y <= y1 + this.height)
		{
			return true;
		}
	}

	return false;
}

// constructor
PIXI.Rectangle.prototype.constructor = PIXI.Rectangle;


/**
 * @author Adrien Brault <adrien.brault@gmail.com>
 */

/**
 * @class Polygon
 * @constructor
 * @param points* {Array<Point>|Array<Number>|Point...|Number...} This can be an array of Points that form the polygon,
 *      a flat array of numbers that will be interpreted as [x,y, x,y, ...], or the arugments passed can be
 *      all the points of the polygon e.g. `new PIXI.Polygon(new PIXI.Point(), new PIXI.Point(), ...)`, or the
 *      arguments passed can be flat x,y values e.g. `new PIXI.Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are
 *      Numbers.
 */
PIXI.Polygon = function(points)
{
    //if points isn't an array, use arguments as the array
    if(!(points instanceof Array))
        points = Array.prototype.slice.call(arguments);

    //if this is a flat array of numbers, convert it to points
    if(typeof points[0] === 'number') {
        var p = [];
        for(var i = 0, il = points.length; i < il; i+=2) {
            p.push(
                new PIXI.Point(points[i], points[i + 1])
            );
        }

        points = p;
    }

	this.points = points;
}

/**
 * Creates a clone of this polygon
 *
 * @method clone
 * @return {Polygon} a copy of the polygon
 */
PIXI.Polygon.prototype.clone = function()
{
	var points = [];
	for (var i=0; i<this.points.length; i++) {
		points.push(this.points[i].clone());
	}

	return new PIXI.Polygon(points);
}

/**
 * Checks if the x, and y coords passed to this function are contained within this polygon
 *
 * @method contains
 * @param x {Number} The X coord of the point to test
 * @param y {Number} The Y coord of the point to test
 * @return {Boolean} if the x/y coords are within this polygon
 */
PIXI.Polygon.prototype.contains = function(x, y)
{
    var inside = false;

    // use some raycasting to test hits
    // https://github.com/substack/point-in-polygon/blob/master/index.js
    for(var i = 0, j = this.points.length - 1; i < this.points.length; j = i++) {
        var xi = this.points[i].x, yi = this.points[i].y,
            xj = this.points[j].x, yj = this.points[j].y,
            intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);

        if(intersect) inside = !inside;
    }

    return inside;
}

PIXI.Polygon.prototype.constructor = PIXI.Polygon;


/**
 * @author Chad Engler <chad@pantherdev.com>
 */

/**
 * The Circle object can be used to specify a hit area for displayobjects
 *
 * @class Circle
 * @constructor
 * @param x {Number} The X coord of the upper-left corner of the framing rectangle of this circle
 * @param y {Number} The Y coord of the upper-left corner of the framing rectangle of this circle
 * @param radius {Number} The radius of the circle
 */
PIXI.Circle = function(x, y, radius)
{
    /**
     * @property x
     * @type Number
     * @default 0
     */
    this.x = x || 0;
    
    /**
     * @property y
     * @type Number
     * @default 0
     */
    this.y = y || 0;

    /**
     * @property radius
     * @type Number
     * @default 0
     */
    this.radius = radius || 0;
}

/**
 * Creates a clone of this Circle instance
 *
 * @method clone
 * @return {Circle} a copy of the polygon
 */
PIXI.Circle.prototype.clone = function()
{
    return new PIXI.Circle(this.x, this.y, this.radius);
}

/**
 * Checks if the x, and y coords passed to this function are contained within this circle
 *
 * @method contains
 * @param x {Number} The X coord of the point to test
 * @param y {Number} The Y coord of the point to test
 * @return {Boolean} if the x/y coords are within this polygon
 */
PIXI.Circle.prototype.contains = function(x, y)
{
    if(this.radius <= 0)
        return false;

    var dx = (this.x - x),
        dy = (this.y - y),
        r2 = this.radius * this.radius;

    dx *= dx;
    dy *= dy;

    return (dx + dy <= r2);
}

PIXI.Circle.prototype.constructor = PIXI.Circle;


/**
 * @author Chad Engler <chad@pantherdev.com>
 */

/**
 * The Ellipse object can be used to specify a hit area for displayobjects
 *
 * @class Ellipse
 * @constructor
 * @param x {Number} The X coord of the upper-left corner of the framing rectangle of this ellipse
 * @param y {Number} The Y coord of the upper-left corner of the framing rectangle of this ellipse
 * @param width {Number} The overall height of this ellipse
 * @param height {Number} The overall width of this ellipse
 */
PIXI.Ellipse = function(x, y, width, height)
{
    /**
     * @property x
     * @type Number
     * @default 0
     */
    this.x = x || 0;
    
    /**
     * @property y
     * @type Number
     * @default 0
     */
    this.y = y || 0;
    
    /**
     * @property width
     * @type Number
     * @default 0
     */
    this.width = width || 0;
    
    /**
     * @property height
     * @type Number
     * @default 0
     */
    this.height = height || 0;
}

/**
 * Creates a clone of this Ellipse instance
 *
 * @method clone
 * @return {Ellipse} a copy of the ellipse
 */
PIXI.Ellipse.prototype.clone = function()
{
    return new PIXI.Ellipse(this.x, this.y, this.width, this.height);
}

/**
 * Checks if the x, and y coords passed to this function are contained within this ellipse
 *
 * @method contains
 * @param x {Number} The X coord of the point to test
 * @param y {Number} The Y coord of the point to test
 * @return {Boolean} if the x/y coords are within this ellipse
 */
PIXI.Ellipse.prototype.contains = function(x, y)
{
    if(this.width <= 0 || this.height <= 0)
        return false;

    //normalize the coords to an ellipse with center 0,0
    //and a radius of 0.5
    var normx = ((x - this.x) / this.width) - 0.5,
        normy = ((y - this.y) / this.height) - 0.5;

    normx *= normx;
    normy *= normy;

    return (normx + normy < 0.25);
}

PIXI.Ellipse.getBounds = function()
{
    return new PIXI.Rectangle(this.x, this.y, this.width, this.height);
}

PIXI.Ellipse.prototype.constructor = PIXI.Ellipse;




/*
 * A lighter version of the rad gl-matrix created by Brandon Jones, Colin MacKenzie IV
 * you both rock!
 */

function determineMatrixArrayType() {
    PIXI.Matrix = (typeof Float32Array !== 'undefined') ? Float32Array : Array;
    return PIXI.Matrix;
}

determineMatrixArrayType();

PIXI.mat3 = {};

PIXI.mat3.create = function()
{
	var matrix = new PIXI.Matrix(9);

	matrix[0] = 1;
	matrix[1] = 0;
	matrix[2] = 0;
	matrix[3] = 0;
	matrix[4] = 1;
	matrix[5] = 0;
	matrix[6] = 0;
	matrix[7] = 0;
	matrix[8] = 1;
	
	return matrix;
}


PIXI.mat3.identity = function(matrix)
{
	matrix[0] = 1;
	matrix[1] = 0;
	matrix[2] = 0;
	matrix[3] = 0;
	matrix[4] = 1;
	matrix[5] = 0;
	matrix[6] = 0;
	matrix[7] = 0;
	matrix[8] = 1;
	
	return matrix;
}


PIXI.mat4 = {};

PIXI.mat4.create = function()
{
	var matrix = new PIXI.Matrix(16);

	matrix[0] = 1;
	matrix[1] = 0;
	matrix[2] = 0;
	matrix[3] = 0;
	matrix[4] = 0;
	matrix[5] = 1;
	matrix[6] = 0;
	matrix[7] = 0;
	matrix[8] = 0;
	matrix[9] = 0;
	matrix[10] = 1;
	matrix[11] = 0;
	matrix[12] = 0;
	matrix[13] = 0;
	matrix[14] = 0;
	matrix[15] = 1;
	
	return matrix;
}

PIXI.mat3.multiply = function (mat, mat2, dest) 
{
	if (!dest) { dest = mat; }
	
	// Cache the matrix values (makes for huge speed increases!)
	var a00 = mat[0], a01 = mat[1], a02 = mat[2],
	    a10 = mat[3], a11 = mat[4], a12 = mat[5],
	    a20 = mat[6], a21 = mat[7], a22 = mat[8],
	
	    b00 = mat2[0], b01 = mat2[1], b02 = mat2[2],
	    b10 = mat2[3], b11 = mat2[4], b12 = mat2[5],
	    b20 = mat2[6], b21 = mat2[7], b22 = mat2[8];
	
	dest[0] = b00 * a00 + b01 * a10 + b02 * a20;
	dest[1] = b00 * a01 + b01 * a11 + b02 * a21;
	dest[2] = b00 * a02 + b01 * a12 + b02 * a22;
	
	dest[3] = b10 * a00 + b11 * a10 + b12 * a20;
	dest[4] = b10 * a01 + b11 * a11 + b12 * a21;
	dest[5] = b10 * a02 + b11 * a12 + b12 * a22;
	
	dest[6] = b20 * a00 + b21 * a10 + b22 * a20;
	dest[7] = b20 * a01 + b21 * a11 + b22 * a21;
	dest[8] = b20 * a02 + b21 * a12 + b22 * a22;
	
	return dest;
}

PIXI.mat3.clone = function(mat)
{
	var matrix = new PIXI.Matrix(9);

	matrix[0] = mat[0];
	matrix[1] = mat[1];
	matrix[2] = mat[2];
	matrix[3] = mat[3];
	matrix[4] = mat[4];
	matrix[5] = mat[5];
	matrix[6] = mat[6];
	matrix[7] = mat[7];
	matrix[8] = mat[8];
	
	return matrix;
}

PIXI.mat3.transpose = function (mat, dest) 
{
 	// If we are transposing ourselves we can skip a few steps but have to cache some values
    if (!dest || mat === dest) {
        var a01 = mat[1], a02 = mat[2],
            a12 = mat[5];

        mat[1] = mat[3];
        mat[2] = mat[6];
        mat[3] = a01;
        mat[5] = mat[7];
        mat[6] = a02;
        mat[7] = a12;
        return mat;
    }

    dest[0] = mat[0];
    dest[1] = mat[3];
    dest[2] = mat[6];
    dest[3] = mat[1];
    dest[4] = mat[4];
    dest[5] = mat[7];
    dest[6] = mat[2];
    dest[7] = mat[5];
    dest[8] = mat[8];
    return dest;
}

PIXI.mat3.toMat4 = function (mat, dest) 
{
	if (!dest) { dest = PIXI.mat4.create(); }
	
	dest[15] = 1;
	dest[14] = 0;
	dest[13] = 0;
	dest[12] = 0;
	
	dest[11] = 0;
	dest[10] = mat[8];
	dest[9] = mat[7];
	dest[8] = mat[6];
	
	dest[7] = 0;
	dest[6] = mat[5];
	dest[5] = mat[4];
	dest[4] = mat[3];
	
	dest[3] = 0;
	dest[2] = mat[2];
	dest[1] = mat[1];
	dest[0] = mat[0];
	
	return dest;
}


/////


PIXI.mat4.create = function()
{
	var matrix = new PIXI.Matrix(16);

	matrix[0] = 1;
	matrix[1] = 0;
	matrix[2] = 0;
	matrix[3] = 0;
	matrix[4] = 0;
	matrix[5] = 1;
	matrix[6] = 0;
	matrix[7] = 0;
	matrix[8] = 0;
	matrix[9] = 0;
	matrix[10] = 1;
	matrix[11] = 0;
	matrix[12] = 0;
	matrix[13] = 0;
	matrix[14] = 0;
	matrix[15] = 1;
	
	return matrix;
}

PIXI.mat4.transpose = function (mat, dest) 
{
	// If we are transposing ourselves we can skip a few steps but have to cache some values
	if (!dest || mat === dest) 
	{
	    var a01 = mat[1], a02 = mat[2], a03 = mat[3],
	        a12 = mat[6], a13 = mat[7],
	        a23 = mat[11];
	
	    mat[1] = mat[4];
	    mat[2] = mat[8];
	    mat[3] = mat[12];
	    mat[4] = a01;
	    mat[6] = mat[9];
	    mat[7] = mat[13];
	    mat[8] = a02;
	    mat[9] = a12;
	    mat[11] = mat[14];
	    mat[12] = a03;
	    mat[13] = a13;
	    mat[14] = a23;
	    return mat;
	}
	
	dest[0] = mat[0];
	dest[1] = mat[4];
	dest[2] = mat[8];
	dest[3] = mat[12];
	dest[4] = mat[1];
	dest[5] = mat[5];
	dest[6] = mat[9];
	dest[7] = mat[13];
	dest[8] = mat[2];
	dest[9] = mat[6];
	dest[10] = mat[10];
	dest[11] = mat[14];
	dest[12] = mat[3];
	dest[13] = mat[7];
	dest[14] = mat[11];
	dest[15] = mat[15];
	return dest;
}

PIXI.mat4.multiply = function (mat, mat2, dest) 
{
	if (!dest) { dest = mat; }
	
	// Cache the matrix values (makes for huge speed increases!)
	var a00 = mat[ 0], a01 = mat[ 1], a02 = mat[ 2], a03 = mat[3];
	var a10 = mat[ 4], a11 = mat[ 5], a12 = mat[ 6], a13 = mat[7];
	var a20 = mat[ 8], a21 = mat[ 9], a22 = mat[10], a23 = mat[11];
	var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15];
	
	// Cache only the current line of the second matrix
    var b0  = mat2[0], b1 = mat2[1], b2 = mat2[2], b3 = mat2[3];  
    dest[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    dest[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    dest[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    dest[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = mat2[4];
    b1 = mat2[5];
    b2 = mat2[6];
    b3 = mat2[7];
    dest[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    dest[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    dest[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    dest[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = mat2[8];
    b1 = mat2[9];
    b2 = mat2[10];
    b3 = mat2[11];
    dest[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    dest[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    dest[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    dest[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = mat2[12];
    b1 = mat2[13];
    b2 = mat2[14];
    b3 = mat2[15];
    dest[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    dest[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    dest[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    dest[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    return dest;
}

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * The base class for all objects that are rendered on the screen.
 *
 * @class DisplayObject
 * @constructor
 */
PIXI.DisplayObject = function()
{
	this.last = this;
	this.first = this;

	/**
	 * The coordinate of the object relative to the local coordinates of the parent.
	 *
	 * @property position
	 * @type Point
	 */
	this.position = new PIXI.Point();

	/**
	 * The scale factor of the object.
	 *
	 * @property scale
	 * @type Point
	 */
	this.scale = new PIXI.Point(1,1);//{x:1, y:1};

	/**
	 * The pivot point of the displayObject that it rotates around
	 *
	 * @property pivot
	 * @type Point
	 */
	this.pivot = new PIXI.Point(0,0);

	/**
	 * The rotation of the object in radians.
	 *
	 * @property rotation
	 * @type Number
	 */
	this.rotation = 0;

	/**
	 * The opacity of the object.
	 *
	 * @property alpha
	 * @type Number
	 */	
	this.alpha = 1;

	/**
	 * The visibility of the object.
	 *
	 * @property visible
	 * @type Boolean
	 */	
	this.visible = true;

	/**
	 * This is the defined area that will pick up mouse / touch events. It is null by default.
	 * Setting it is a neat way of optimising the hitTest function that the interactionManager will use (as it will not need to hit test all the children)
	 *
	 * @property hitArea
	 * @type Rectangle|Circle|Ellipse|Polygon
	 */	
	this.hitArea = null;

	/**
	 * This is used to indicate if the displayObject should display a mouse hand cursor on rollover
	 *
	 * @property buttonMode
	 * @type Boolean
	 */
	this.buttonMode = false;

	/**
	 * Can this object be rendered
	 *
	 * @property renderable
	 * @type Boolean
	 */
	this.renderable = false;

	/**
	 * [read-only] The display object container that contains this display object.
	 *
	 * @property parent
	 * @type DisplayObjectContainer
	 * @readOnly
	 */	
	this.parent = null;

	/**
	 * [read-only] The stage the display object is connected to, or undefined if it is not connected to the stage.
	 *
	 * @property stage
	 * @type Stage
	 * @readOnly
	 */	
	this.stage = null;

	/**
	 * [read-only] The multiplied alpha of the displayobject
	 *
	 * @property worldAlpha
	 * @type Number
	 * @readOnly
	 */
	this.worldAlpha = 1;

	/**
	 * [read-only] Whether or not the object is interactive, do not toggle directly! use the `interactive` property
	 *
	 * @property _interactive
	 * @type Boolean
	 * @readOnly
	 * @private
	 */
	this._interactive = false;

	/**
	 * [read-only] Current transform of the object based on world (parent) factors
	 *
	 * @property worldTransform
	 * @type Mat3
	 * @readOnly
	 * @private
	 */
	this.worldTransform = PIXI.mat3.create()//mat3.identity();

	/**
	 * [read-only] Current transform of the object locally
	 *
	 * @property localTransform
	 * @type Mat3
	 * @readOnly
	 * @private
	 */
	this.localTransform = PIXI.mat3.create()//mat3.identity();

	/**
	 * [NYI] Unkown
	 *
	 * @property color
	 * @type Array<>
	 * @private
	 */
	this.color = [];

	/**
	 * [NYI] Holds whether or not this object is dynamic, for rendering optimization
	 *
	 * @property dynamic
	 * @type Boolean
	 * @private
	 */
	this.dynamic = true;

	// chach that puppy!
	this._sr = 0;
	this._cr = 1;

	/*
	 * MOUSE Callbacks
	 */

	/**
	 * A callback that is used when the users clicks on the displayObject with their mouse
	 * @method click
	 * @param interactionData {InteractionData}
	 */

	/**
	 * A callback that is used when the user clicks the mouse down over the sprite
	 * @method mousedown
	 * @param interactionData {InteractionData}
	 */

	/**
	 * A callback that is used when the user releases the mouse that was over the displayObject
	 * for this callback to be fired the mouse must have been pressed down over the displayObject
	 * @method mouseup
	 * @param interactionData {InteractionData}
	 */

	/**
	 * A callback that is used when the user releases the mouse that was over the displayObject but is no longer over the displayObject
	 * for this callback to be fired, The touch must have started over the displayObject
	 * @method mouseupoutside
	 * @param interactionData {InteractionData}
	 */

	/**
	 * A callback that is used when the users mouse rolls over the displayObject
	 * @method mouseover
	 * @param interactionData {InteractionData}
	 */

	/**
	 * A callback that is used when the users mouse leaves the displayObject
	 * @method mouseout
	 * @param interactionData {InteractionData}
	 */


	/*
	 * TOUCH Callbacks
	 */

	/**
	 * A callback that is used when the users taps on the sprite with their finger
	 * basically a touch version of click
	 * @method tap
	 * @param interactionData {InteractionData}
	 */

	/**
	 * A callback that is used when the user touch's over the displayObject
	 * @method touchstart
	 * @param interactionData {InteractionData}
	 */

	/**
	 * A callback that is used when the user releases a touch over the displayObject
	 * @method touchend
	 * @param interactionData {InteractionData}
	 */

	/**
	 * A callback that is used when the user releases the touch that was over the displayObject
	 * for this callback to be fired, The touch must have started over the sprite
	 * @method touchendoutside
	 * @param interactionData {InteractionData}
	 */
}

// constructor
PIXI.DisplayObject.prototype.constructor = PIXI.DisplayObject;

/**
 * [Deprecated] Indicates if the sprite will have touch and mouse interactivity. It is false by default
 * Instead of using this function you can now simply set the interactive property to true or false
 *
 * @method setInteractive
 * @param interactive {Boolean}
 * @deprecated Simply set the `interactive` property directly
 */
PIXI.DisplayObject.prototype.setInteractive = function(interactive)
{
	this.interactive = interactive;
}

/**
 * Indicates if the sprite will have touch and mouse interactivity. It is false by default
 *
 * @property interactive
 * @type Boolean
 * @default false
 */
Object.defineProperty(PIXI.DisplayObject.prototype, 'interactive', {
    get: function() {
        return this._interactive;
    },
    set: function(value) {
    	this._interactive = value;
    	
    	// TODO more to be done here..
		// need to sort out a re-crawl!
		if(this.stage)this.stage.dirty = true;
    }
});

/**
 * Sets a mask for the displayObject. A mask is an object that limits the visibility of an object to the shape of the mask applied to it.
 * In PIXI a regular mask must be a PIXI.Ggraphics object. This allows for much faster masking in canvas as it utilises shape clipping.
 * To remove a mask, set this property to null.
 *
 * @property mask
 * @type Graphics
 */
Object.defineProperty(PIXI.DisplayObject.prototype, 'mask', {
    get: function() {
        return this._mask;
    },
    set: function(value) {
    	
        this._mask = value;
        
        if(value)
        {
	        this.addFilter(value)
        }
        else
        {
        	 this.removeFilter();
        }
    }
});

/*
 * Adds a filter to this displayObject
 *
 * @method addFilter
 * @param mask {Graphics} the graphics object to use as a filter
 * @private
 */
PIXI.DisplayObject.prototype.addFilter = function(mask)
{
	if(this.filter)return;
	this.filter = true;
	
	// insert a filter block..
	var start = new PIXI.FilterBlock();
	var end = new PIXI.FilterBlock();
	
	start.mask = mask;
	end.mask = mask;
	
	start.first = start.last =  this;
	end.first = end.last = this;
	
	start.open = true;
	
	/*
	 * insert start
	 */
	
	var childFirst = start
	var childLast = start
	var nextObject;
	var previousObject;
		
	previousObject = this.first._iPrev;
	
	if(previousObject)
	{
		nextObject = previousObject._iNext;
		childFirst._iPrev = previousObject;
		previousObject._iNext = childFirst;		
	}
	else
	{
		nextObject = this;
	}	
	
	if(nextObject)
	{
		nextObject._iPrev = childLast;
		childLast._iNext = nextObject;
	}
	
	
	// now insert the end filter block..
	
	/*
	 * insert end filter
	 */
	var childFirst = end
	var childLast = end
	var nextObject = null;
	var previousObject = null;
		
	previousObject = this.last;
	nextObject = previousObject._iNext;
	
	if(nextObject)
	{
		nextObject._iPrev = childLast;
		childLast._iNext = nextObject;
	}
	
	childFirst._iPrev = previousObject;
	previousObject._iNext = childFirst;	
	
	var updateLast = this;
	
	var prevLast = this.last;
	while(updateLast)
	{
		if(updateLast.last == prevLast)
		{
			updateLast.last = end;
		}
		updateLast = updateLast.parent;
	}
	
	this.first = start;
	
	// if webGL...
	if(this.__renderGroup)
	{
		this.__renderGroup.addFilterBlocks(start, end);
	}
	
	mask.renderable = false;
	
}

/*
 * Removes the filter to this displayObject
 *
 * @method removeFilter
 * @private
 */
PIXI.DisplayObject.prototype.removeFilter = function()
{
	if(!this.filter)return;
	this.filter = false;
	
	// modify the list..
	var startBlock = this.first;
	
	var nextObject = startBlock._iNext;
	var previousObject = startBlock._iPrev;
		
	if(nextObject)nextObject._iPrev = previousObject;
	if(previousObject)previousObject._iNext = nextObject;		
	
	this.first = startBlock._iNext;
	
	
	// remove the end filter
	var lastBlock = this.last;
	
	var nextObject = lastBlock._iNext;
	var previousObject = lastBlock._iPrev;
		
	if(nextObject)nextObject._iPrev = previousObject;
	previousObject._iNext = nextObject;		
	
	// this is always true too!
	var tempLast =  lastBlock._iPrev;	
	// need to make sure the parents last is updated too
	var updateLast = this;
	while(updateLast.last == lastBlock)
	{
		updateLast.last = tempLast;
		updateLast = updateLast.parent;
		if(!updateLast)break;
	}
	
	var mask = startBlock.mask
	mask.renderable = true;
	
	// if webGL...
	if(this.__renderGroup)
	{
		this.__renderGroup.removeFilterBlocks(startBlock, lastBlock);
	}
}

/*
 * Updates the object transform for rendering
 *
 * @method updateTransform
 * @private
 */
PIXI.DisplayObject.prototype.updateTransform = function()
{
	// TODO OPTIMIZE THIS!! with dirty
	if(this.rotation !== this.rotationCache)
	{
		this.rotationCache = this.rotation;
		this._sr =  Math.sin(this.rotation);
		this._cr =  Math.cos(this.rotation);
	}	
	
	var localTransform = this.localTransform;
	var parentTransform = this.parent.worldTransform;
	var worldTransform = this.worldTransform;
	//console.log(localTransform)
	localTransform[0] = this._cr * this.scale.x;
	localTransform[1] = -this._sr * this.scale.y
	localTransform[3] = this._sr * this.scale.x;
	localTransform[4] = this._cr * this.scale.y;
	
	// TODO --> do we even need a local matrix???
	
	var px = this.pivot.x;
	var py = this.pivot.y;
   	
    // Cache the matrix values (makes for huge speed increases!)
    var a00 = localTransform[0], a01 = localTransform[1], a02 = this.position.x - localTransform[0] * px - py * localTransform[1],
        a10 = localTransform[3], a11 = localTransform[4], a12 = this.position.y - localTransform[4] * py - px * localTransform[3],

        b00 = parentTransform[0], b01 = parentTransform[1], b02 = parentTransform[2],
        b10 = parentTransform[3], b11 = parentTransform[4], b12 = parentTransform[5];

	localTransform[2] = a02
	localTransform[5] = a12
	
    worldTransform[0] = b00 * a00 + b01 * a10;
    worldTransform[1] = b00 * a01 + b01 * a11;
    worldTransform[2] = b00 * a02 + b01 * a12 + b02;

    worldTransform[3] = b10 * a00 + b11 * a10;
    worldTransform[4] = b10 * a01 + b11 * a11;
    worldTransform[5] = b10 * a02 + b11 * a12 + b12;

	// because we are using affine transformation, we can optimise the matrix concatenation process.. wooo!
	// mat3.multiply(this.localTransform, this.parent.worldTransform, this.worldTransform);
	this.worldAlpha = this.alpha * this.parent.worldAlpha;
	
	this.vcount = PIXI.visibleCount;

}

PIXI.visibleCount = 0;
/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */


/**
 * A DisplayObjectContainer represents a collection of display objects.
 * It is the base class of all display objects that act as a container for other objects.
 *
 * @class DisplayObjectContainer 
 * @extends DisplayObject
 * @constructor
 */
PIXI.DisplayObjectContainer = function()
{
	PIXI.DisplayObject.call( this );
	
	/**
	 * [read-only] The of children of this container.
	 *
	 * @property children
	 * @type Array<DisplayObject>
	 * @readOnly
	 */	
	this.children = [];
}

// constructor
PIXI.DisplayObjectContainer.prototype = Object.create( PIXI.DisplayObject.prototype );
PIXI.DisplayObjectContainer.prototype.constructor = PIXI.DisplayObjectContainer;

//TODO make visible a getter setter
/*
Object.defineProperty(PIXI.DisplayObjectContainer.prototype, 'visible', {
    get: function() {
        return this._visible;
    },
    set: function(value) {
        this._visible = value;
        
    }
});*/

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param child {DisplayObject} The DisplayObject to add to the container
 */
PIXI.DisplayObjectContainer.prototype.addChild = function(child)
{
	if(child.parent != undefined)
	{
		
		//// COULD BE THIS???
		child.parent.removeChild(child);
	//	return;
	}

	child.parent = this;
	
	this.children.push(child);	
	
	// update the stage refference..
	
	if(this.stage)
	{
		var tmpChild = child;
		do
		{
			if(tmpChild.interactive)this.stage.dirty = true;
			tmpChild.stage = this.stage;
			tmpChild = tmpChild._iNext;
		}	
		while(tmpChild)
	}
	
	// LINKED LIST //
	
	// modify the list..
	var childFirst = child.first
	var childLast = child.last;
	var nextObject;
	var previousObject;
	
	// this could be wrong if there is a filter??
	if(this.filter)
	{
		previousObject =  this.last._iPrev;
	}
	else
	{
		previousObject = this.last;
	}

	nextObject = previousObject._iNext;
	
	// always true in this case
	// need to make sure the parents last is updated too
	var updateLast = this;
	var prevLast = previousObject;
	
	while(updateLast)
	{
		if(updateLast.last == prevLast)
		{
			updateLast.last = child.last;
		}
		updateLast = updateLast.parent;
	}
	
	if(nextObject)
	{
		nextObject._iPrev = childLast;
		childLast._iNext = nextObject;
	}
	
	childFirst._iPrev = previousObject;
	previousObject._iNext = childFirst;		

	// need to remove any render groups..
	if(this.__renderGroup)
	{
		// being used by a renderTexture.. if it exists then it must be from a render texture;
		if(child.__renderGroup)child.__renderGroup.removeDisplayObjectAndChildren(child);
		// add them to the new render group..
		this.__renderGroup.addDisplayObjectAndChildren(child);
	}
	
}

/**
 * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown
 *
 * @method addChildAt
 * @param child {DisplayObject} The child to add
 * @param index {Number} The index to place the child in
 */
PIXI.DisplayObjectContainer.prototype.addChildAt = function(child, index)
{
	if(index >= 0 && index <= this.children.length)
	{
		if(child.parent != undefined)
		{
			child.parent.removeChild(child);
		}
		child.parent = this;
		
		if(this.stage)
		{
			var tmpChild = child;
			do
			{
				if(tmpChild.interactive)this.stage.dirty = true;
				tmpChild.stage = this.stage;
				tmpChild = tmpChild._iNext;
			}
			while(tmpChild)
		}
		
		// modify the list..
		var childFirst = child.first;
		var childLast = child.last;
		var nextObject;
		var previousObject;
		
		if(index == this.children.length)
		{
			previousObject =  this.last;
			var updateLast = this;
			var prevLast = this.last;
			while(updateLast)
			{
				if(updateLast.last == prevLast)
				{
					updateLast.last = child.last;
				}
				updateLast = updateLast.parent;
			}
		}
		else if(index == 0)
		{
			previousObject = this;
		}
		else
		{
			previousObject = this.children[index-1].last;
		}
		
		nextObject = previousObject._iNext;
		
		// always true in this case
		if(nextObject)
		{
			nextObject._iPrev = childLast;
			childLast._iNext = nextObject;
		}
		
		childFirst._iPrev = previousObject;
		previousObject._iNext = childFirst;		

		this.children.splice(index, 0, child);
		// need to remove any render groups..
		if(this.__renderGroup)
		{
			// being used by a renderTexture.. if it exists then it must be from a render texture;
			if(child.__renderGroup)child.__renderGroup.removeDisplayObjectAndChildren(child);
			// add them to the new render group..
			this.__renderGroup.addDisplayObjectAndChildren(child);
		}
		
	}
	else
	{
		throw new Error(child + " The index "+ index +" supplied is out of bounds " + this.children.length);
	}
}

/**
 * [NYI] Swaps the depth of 2 displayObjects
 *
 * @method swapChildren
 * @param child {DisplayObject}
 * @param child2 {DisplayObject}
 * @private
 */
PIXI.DisplayObjectContainer.prototype.swapChildren = function(child, child2)
{
	/*
	 * this funtion needs to be recoded.. 
	 * can be done a lot faster..
	 */
	return;
	
	// need to fix this function :/
	/*
	// TODO I already know this??
	var index = this.children.indexOf( child );
	var index2 = this.children.indexOf( child2 );
	
	if ( index !== -1 && index2 !== -1 ) 
	{
		// cool
		
		/*
		if(this.stage)
		{
			// this is to satisfy the webGL batching..
			// TODO sure there is a nicer way to achieve this!
			this.stage.__removeChild(child);
			this.stage.__removeChild(child2);
			
			this.stage.__addChild(child);
			this.stage.__addChild(child2);
		}
		
		// swap the positions..
		this.children[index] = child2;
		this.children[index2] = child;
		
	}
	else
	{
		throw new Error(child + " Both the supplied DisplayObjects must be a child of the caller " + this);
	}*/
}

/**
 * Returns the Child at the specified index
 *
 * @method getChildAt
 * @param index {Number} The index to get the child from
 */
PIXI.DisplayObjectContainer.prototype.getChildAt = function(index)
{
	if(index >= 0 && index < this.children.length)
	{
		return this.children[index];
	}
	else
	{
		throw new Error(child + " Both the supplied DisplayObjects must be a child of the caller " + this);
	}
}

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param child {DisplayObject} The DisplayObject to remove
 */
PIXI.DisplayObjectContainer.prototype.removeChild = function(child)
{
	var index = this.children.indexOf( child );
	if ( index !== -1 ) 
	{
		// unlink //
		// modify the list..
		var childFirst = child.first;
		var childLast = child.last;
		
		var nextObject = childLast._iNext;
		var previousObject = childFirst._iPrev;
			
		if(nextObject)nextObject._iPrev = previousObject;
		previousObject._iNext = nextObject;		
		
		if(this.last == childLast)
		{
			var tempLast =  childFirst._iPrev;	
			// need to make sure the parents last is updated too
			var updateLast = this;
			while(updateLast.last == childLast.last)
			{
				updateLast.last = tempLast;
				updateLast = updateLast.parent;
				if(!updateLast)break;
			}
		}
		
		childLast._iNext = null;
		childFirst._iPrev = null;
		 
		// update the stage reference..
		if(this.stage)
		{
			var tmpChild = child;
			do
			{
				if(tmpChild.interactive)this.stage.dirty = true;
				tmpChild.stage = null;
				tmpChild = tmpChild._iNext;
			}	
			while(tmpChild)
		}
	
		// webGL trim
		if(child.__renderGroup)
		{
			child.__renderGroup.removeDisplayObjectAndChildren(child);
		}
		
		child.parent = undefined;
		this.children.splice( index, 1 );
	}
	else
	{
		throw new Error(child + " The supplied DisplayObject must be a child of the caller " + this);
	}
}

/*
 * Updates the container's children's transform for rendering
 *
 * @method updateTransform
 * @private
 */
PIXI.DisplayObjectContainer.prototype.updateTransform = function()
{
	if(!this.visible)return;
	
	PIXI.DisplayObject.prototype.updateTransform.call( this );
	
	for(var i=0,j=this.children.length; i<j; i++)
	{
		this.children[i].updateTransform();	
	}
}

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

PIXI.blendModes = {};
PIXI.blendModes.NORMAL = 0;
PIXI.blendModes.SCREEN = 1;


/**
 * The SPrite object is the base for all textured objects that are rendered to the screen
 *
 * @class Sprite
 * @extends DisplayObjectContainer
 * @constructor
 * @param texture {Texture} The texture for this sprite
 * @type String
 */
PIXI.Sprite = function(texture)
{
	PIXI.DisplayObjectContainer.call( this );

	/**
	 * The anchor sets the origin point of the texture.
	 * The default is 0,0 this means the textures origin is the top left 
	 * Setting than anchor to 0.5,0.5 means the textures origin is centered
	 * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right
	 *
     * @property anchor
     * @type Point
     */
	this.anchor = new PIXI.Point();

	/**
	 * The texture that the sprite is using
	 *
	 * @property texture
	 * @type Texture
	 */
	this.texture = texture;

	/**
	 * The blend mode of sprite.
	 * currently supports PIXI.blendModes.NORMAL and PIXI.blendModes.SCREEN
	 *
	 * @property blendMode
	 * @type Number
	 */
	this.blendMode = PIXI.blendModes.NORMAL;

	/**
	 * The width of the sprite (this is initially set by the texture)
	 *
	 * @property _width
	 * @type Number
	 * @private
	 */
	this._width = 0;

	/**
	 * The height of the sprite (this is initially set by the texture)
	 *
	 * @property _height
	 * @type Number
	 * @private
	 */
	this._height = 0;

	if(texture.baseTexture.hasLoaded)
	{
		this.updateFrame = true;
	}
	else
	{
		this.onTextureUpdateBind = this.onTextureUpdate.bind(this);
		this.texture.addEventListener( 'update', this.onTextureUpdateBind );
	}

	this.renderable = true;
}

// constructor
PIXI.Sprite.prototype = Object.create( PIXI.DisplayObjectContainer.prototype );
PIXI.Sprite.prototype.constructor = PIXI.Sprite;

/**
 * The width of the sprite, setting this will actually modify the scale to acheive the value set
 *
 * @property width
 * @type Number
 */
Object.defineProperty(PIXI.Sprite.prototype, 'width', {
    get: function() {
        return this.scale.x * this.texture.frame.width;
    },
    set: function(value) {
    	this.scale.x = value / this.texture.frame.width
        this._width = value;
    }
});

/**
 * The height of the sprite, setting this will actually modify the scale to acheive the value set
 *
 * @property height
 * @type Number
 */
Object.defineProperty(PIXI.Sprite.prototype, 'height', {
    get: function() {
        return  this.scale.y * this.texture.frame.height;
    },
    set: function(value) {
    	this.scale.y = value / this.texture.frame.height
        this._height = value;
    }
});

/**
 * Sets the texture of the sprite
 *
 * @method setTexture
 * @param texture {Texture} The PIXI texture that is displayed by the sprite
 */
PIXI.Sprite.prototype.setTexture = function(texture)
{
	// stop current texture;
	if(this.texture.baseTexture != texture.baseTexture)
	{
		this.textureChange = true;	
		this.texture = texture;
		
		if(this.__renderGroup)
		{
			this.__renderGroup.updateTexture(this);
		}
	}
	else
	{
		this.texture = texture;
	}
	
	this.updateFrame = true;
}

/**
 * When the texture is updated, this event will fire to update the scale and frame
 *
 * @method onTextureUpdate
 * @param event
 * @private
 */
PIXI.Sprite.prototype.onTextureUpdate = function(event)
{
	//this.texture.removeEventListener( 'update', this.onTextureUpdateBind );
	
	// so if _width is 0 then width was not set..
	if(this._width)this.scale.x = this._width / this.texture.frame.width;
	if(this._height)this.scale.y = this._height / this.texture.frame.height;
	
	this.updateFrame = true;
}

// some helper functions..

/**
 * 
 * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId
 * The frame ids are created when a Texture packer file has been loaded
 *
 * @method fromFrame
 * @static
 * @param frameId {String} The frame Id of the texture in the cache
 * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId
 */
PIXI.Sprite.fromFrame = function(frameId)
{
	var texture = PIXI.TextureCache[frameId];
	if(!texture)throw new Error("The frameId '"+ frameId +"' does not exist in the texture cache" + this);
	return new PIXI.Sprite(texture);
}

/**
 * 
 * Helper function that creates a sprite that will contain a texture based on an image url
 * If the image is not in the texture cache it will be loaded
 *
 * @method fromImage
 * @static
 * @param imageId {String} The image url of the texture
 * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id
 */
PIXI.Sprite.fromImage = function(imageId)
{
	var texture = PIXI.Texture.fromImage(imageId);
	return new PIXI.Sprite(texture);
}


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * A MovieClip is a simple way to display an animation depicted by a list of textures.
 *
 * @class MovieClip
 * @extends Sprite
 * @constructor
 * @param textures {Array<Texture>} an array of {Texture} objects that make up the animation
 */
PIXI.MovieClip = function(textures)
{
	PIXI.Sprite.call(this, textures[0]);
	
	/**
	 * The array of textures that make up the animation
	 *
	 * @property textures
	 * @type Array
	 */
	this.textures = textures;
	
	/**
	 * The speed that the MovieClip will play at. Higher is faster, lower is slower
	 *
	 * @property animationSpeed
	 * @type Number
	 * @default 1
	 */
	this.animationSpeed = 1;

	/**
	 * Whether or not the movie clip repeats after playing.
	 *
	 * @property loop
	 * @type Boolean
	 * @default true
	 */
	this.loop = true;

	/**
	 * Function to call when a MovieClip finishes playing
	 *
	 * @property onComplete
	 * @type Function
	 */
	this.onComplete = null;
	
	/**
	 * [read-only] The index MovieClips current frame (this may not have to be a whole number)
	 *
	 * @property currentFrame
	 * @type Number
	 * @default 0
	 * @readOnly
	 */
	this.currentFrame = 0; 
	
	/**
	 * [read-only] Indicates if the MovieClip is currently playing
	 *
	 * @property playing
	 * @type Boolean
	 * @readOnly
	 */
	this.playing = false;
}

// constructor
PIXI.MovieClip.prototype = Object.create( PIXI.Sprite.prototype );
PIXI.MovieClip.prototype.constructor = PIXI.MovieClip;

/**
 * Stops the MovieClip
 *
 * @method stop
 */
PIXI.MovieClip.prototype.stop = function()
{
	this.playing = false;
}

/**
 * Plays the MovieClip
 *
 * @method play
 */
PIXI.MovieClip.prototype.play = function()
{
	this.playing = true;
}

/**
 * Stops the MovieClip and goes to a specific frame
 *
 * @method gotoAndStop
 * @param frameNumber {Number} frame index to stop at
 */
PIXI.MovieClip.prototype.gotoAndStop = function(frameNumber)
{
	this.playing = false;
	this.currentFrame = frameNumber;
	var round = (this.currentFrame + 0.5) | 0;
	this.setTexture(this.textures[round % this.textures.length]);
}

/**
 * Goes to a specific frame and begins playing the MovieClip
 *
 * @method gotoAndPlay
 * @param frameNumber {Number} frame index to start at
 */
PIXI.MovieClip.prototype.gotoAndPlay = function(frameNumber)
{
	this.currentFrame = frameNumber;
	this.playing = true;
}

/*
 * Updates the object transform for rendering
 *
 * @method updateTransform
 * @private
 */
PIXI.MovieClip.prototype.updateTransform = function()
{
	PIXI.Sprite.prototype.updateTransform.call(this);
	
	if(!this.playing)return;
	
	this.currentFrame += this.animationSpeed;
	
	var round = (this.currentFrame + 0.5) | 0;
	
	if(this.loop || round < this.textures.length)
	{
		this.setTexture(this.textures[round % this.textures.length]);
	}
	else if(round >= this.textures.length)
	{
		this.gotoAndStop(this.textures.length - 1);
		if(this.onComplete)
		{
			this.onComplete();
		}
	}
}
/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */



PIXI.FilterBlock = function(mask)
{
	this.graphics = mask
	this.visible = true;
	this.renderable = true;
}


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * A Text Object will create a line(s) of text to split a line you can use "\n"
 *
 * @class Text
 * @extends Sprite
 * @constructor
 * @param text {String} The copy that you would like the text to display
 * @param [style] {Object} The style parameters
 * @param [style.font] {String} default "bold 20pt Arial" The style and size of the font
 * @param [style.fill="black"] {Object} A canvas fillstyle that will be used on the text eg "red", "#00FF00"
 * @param [style.align="left"] {String} An alignment of the multiline text ("left", "center" or "right")
 * @param [style.stroke] {String} A canvas fillstyle that will be used on the text stroke eg "blue", "#FCFF00"
 * @param [style.strokeThickness=0] {Number} A number that represents the thickness of the stroke. Default is 0 (no stroke)
 * @param [style.wordWrap=false] {Boolean} Indicates if word wrap should be used
 * @param [style.wordWrapWidth=100] {Number} The width at which text will wrap
 */
PIXI.Text = function(text, style)
{
    this.canvas = document.createElement("canvas");
    this.context = this.canvas.getContext("2d");
    PIXI.Sprite.call(this, PIXI.Texture.fromCanvas(this.canvas));

    this.setText(text);
    this.setStyle(style);
    
    this.updateText();
    this.dirty = false;
};

// constructor
PIXI.Text.prototype = Object.create(PIXI.Sprite.prototype);
PIXI.Text.prototype.constructor = PIXI.Text;

/**
 * Set the style of the text
 *
 * @method setStyle
 * @param [style] {Object} The style parameters
 * @param [style.font="bold 20pt Arial"] {String} The style and size of the font
 * @param [style.fill="black"] {Object} A canvas fillstyle that will be used on the text eg "red", "#00FF00"
 * @param [style.align="left"] {String} An alignment of the multiline text ("left", "center" or "right")
 * @param [style.stroke="black"] {String} A canvas fillstyle that will be used on the text stroke eg "blue", "#FCFF00"
 * @param [style.strokeThickness=0] {Number} A number that represents the thickness of the stroke. Default is 0 (no stroke)
 * @param [style.wordWrap=false] {Boolean} Indicates if word wrap should be used
 * @param [style.wordWrapWidth=100] {Number} The width at which text will wrap
 */
PIXI.Text.prototype.setStyle = function(style)
{
    style = style || {};
    style.font = style.font || "bold 20pt Arial";
    style.fill = style.fill || "black";
    style.align = style.align || "left";
    style.stroke = style.stroke || "black"; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136
    style.strokeThickness = style.strokeThickness || 0;
    style.wordWrap = style.wordWrap || false;
    style.wordWrapWidth = style.wordWrapWidth || 100;
    this.style = style;
    this.dirty = true;
};

/**
 * Set the copy for the text object. To split a line you can use "\n"
 *
 * @methos setText
 * @param {String} text The copy that you would like the text to display
 */
PIXI.Sprite.prototype.setText = function(text)
{
    this.text = text.toString() || " ";
    this.dirty = true;
};

/**
 * Renders text
 *
 * @method updateText
 * @private
 */
PIXI.Text.prototype.updateText = function()
{
	this.context.font = this.style.font;
	
	var outputText = this.text;
	
	// word wrap
	// preserve original text
	if(this.style.wordWrap)outputText = this.wordWrap(this.text);

	//split text into lines
	var lines = outputText.split(/(?:\r\n|\r|\n)/);

	//calculate text width
	var lineWidths = [];
	var maxLineWidth = 0;
	for (var i = 0; i < lines.length; i++)
	{
		var lineWidth = this.context.measureText(lines[i]).width;
		lineWidths[i] = lineWidth;
		maxLineWidth = Math.max(maxLineWidth, lineWidth);
	}
	this.canvas.width = maxLineWidth + this.style.strokeThickness;
	
	//calculate text height
	var lineHeight = this.determineFontHeight("font: " + this.style.font  + ";") + this.style.strokeThickness;
	this.canvas.height = lineHeight * lines.length;

	//set canvas text styles
	this.context.fillStyle = this.style.fill;
	this.context.font = this.style.font;
	
	this.context.strokeStyle = this.style.stroke;
	this.context.lineWidth = this.style.strokeThickness;

	this.context.textBaseline = "top";

	//draw lines line by line
	for (i = 0; i < lines.length; i++)
	{
		var linePosition = new PIXI.Point(this.style.strokeThickness / 2, this.style.strokeThickness / 2 + i * lineHeight);
	
		if(this.style.align == "right")
		{
			linePosition.x += maxLineWidth - lineWidths[i];
		}
		else if(this.style.align == "center")
		{
			linePosition.x += (maxLineWidth - lineWidths[i]) / 2;
		}

		if(this.style.stroke && this.style.strokeThickness)
		{
			this.context.strokeText(lines[i], linePosition.x, linePosition.y);
		}

		if(this.style.fill)
		{
			this.context.fillText(lines[i], linePosition.x, linePosition.y);
		}
	}
	
    this.updateTexture();
};

/**
 * Updates texture size based on canvas size
 *
 * @method updateTexture
 * @private
 */
PIXI.Text.prototype.updateTexture = function()
{
    this.texture.baseTexture.width = this.canvas.width;
    this.texture.baseTexture.height = this.canvas.height;
    this.texture.frame.width = this.canvas.width;
    this.texture.frame.height = this.canvas.height;
    
  	this._width = this.canvas.width;
    this._height = this.canvas.height;
	
    PIXI.texturesToUpdate.push(this.texture.baseTexture);
};

/**
 * Updates the transfor of this object
 *
 * @method updateTransform
 * @private
 */
PIXI.Text.prototype.updateTransform = function()
{
	if(this.dirty)
	{
		this.updateText();	
		this.dirty = false;
	}
	
	PIXI.Sprite.prototype.updateTransform.call(this);
};

/*
 * http://stackoverflow.com/users/34441/ellisbben
 * great solution to the problem!
 *
 * @method determineFontHeight
 * @param fontStyle {Object}
 * @private
 */
PIXI.Text.prototype.determineFontHeight = function(fontStyle) 
{
	// build a little reference dictionary so if the font style has been used return a
	// cached version...
	var result = PIXI.Text.heightCache[fontStyle];
	
	if(!result)
	{
		var body = document.getElementsByTagName("body")[0];
		var dummy = document.createElement("div");
		var dummyText = document.createTextNode("M");
		dummy.appendChild(dummyText);
		dummy.setAttribute("style", fontStyle + ';position:absolute;top:0;left:0');
		body.appendChild(dummy);
		
		result = dummy.offsetHeight;
		PIXI.Text.heightCache[fontStyle] = result;
		
		body.removeChild(dummy);
	}
	
	return result;
};

/**
 * A Text Object will apply wordwrap
 *
 * @method wordWrap
 * @param text {String}
 * @private
 */
PIXI.Text.prototype.wordWrap = function(text)
{
	// search good wrap position
	var searchWrapPos = function(ctx, text, start, end, wrapWidth)
	{
		var p = Math.floor((end-start) / 2) + start;
		if(p == start) {
			return 1;
		}
		
		if(ctx.measureText(text.substring(0,p)).width <= wrapWidth)
		{
			if(ctx.measureText(text.substring(0,p+1)).width > wrapWidth)
			{
				return p;
			}
			else
			{
				return arguments.callee(ctx, text, p, end, wrapWidth);
			}
		}
		else
		{
			return arguments.callee(ctx, text, start, p, wrapWidth);
		}
	};
	 
	var lineWrap = function(ctx, text, wrapWidth)
	{
		if(ctx.measureText(text).width <= wrapWidth || text.length < 1)
		{
			return text;
		}
		var pos = searchWrapPos(ctx, text, 0, text.length, wrapWidth);
		return text.substring(0, pos) + "\n" + arguments.callee(ctx, text.substring(pos), wrapWidth);
	};
	
	var result = "";
	var lines = text.split("\n");
	for (var i = 0; i < lines.length; i++)
	{
		result += lineWrap(this.context, lines[i], this.style.wordWrapWidth) + "\n";
	}
	
	return result;
};

/**
 * Destroys this text object
 *
 * @method destroy
 * @param destroyTexture {Boolean}
 */
PIXI.Text.prototype.destroy = function(destroyTexture)
{
	if(destroyTexture)
	{
		this.texture.destroy();
	}
		
};

PIXI.Text.heightCache = {};

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * A Text Object will create a line(s) of text using bitmap font. To split a line you can use "\n", "\r" or "\r\n"
 * You can generate the fnt files using 
 * http://www.angelcode.com/products/bmfont/ for windows or
 * http://www.bmglyph.com/ for mac.
 *
 * @class BitmapText
 * @extends DisplayObjectContainer
 * @constructor
 * @param text {String} The copy that you would like the text to display
 * @param style {Object} The style parameters
 * @param style.font {String} The size (optional) and bitmap font id (required) eq "Arial" or "20px Arial" (must have loaded previously)
 * @param [style.align="left"] {String} An alignment of the multiline text ("left", "center" or "right")
 */
PIXI.BitmapText = function(text, style)
{
    PIXI.DisplayObjectContainer.call(this);

    this.setText(text);
    this.setStyle(style);
    this.updateText();
    this.dirty = false

};

// constructor
PIXI.BitmapText.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
PIXI.BitmapText.prototype.constructor = PIXI.BitmapText;

/**
 * Set the copy for the text object
 *
 * @method setText
 * @param text {String} The copy that you would like the text to display
 */
PIXI.BitmapText.prototype.setText = function(text)
{
    this.text = text || " ";
    this.dirty = true;
};

/**
 * Set the style of the text
 *
 * @method setStyle
 * @param style {Object} The style parameters
 * @param style.font {String} The size (optional) and bitmap font id (required) eq "Arial" or "20px Arial" (must have loaded previously)
 * @param [style.align="left"] {String} An alignment of the multiline text ("left", "center" or "right")
 */
PIXI.BitmapText.prototype.setStyle = function(style)
{
    style = style || {};
    style.align = style.align || "left";
    this.style = style;

    var font = style.font.split(" ");
    this.fontName = font[font.length - 1];
    this.fontSize = font.length >= 2 ? parseInt(font[font.length - 2], 10) : PIXI.BitmapText.fonts[this.fontName].size;

    this.dirty = true;
};

/**
 * Renders text
 *
 * @method updateText
 * @private
 */
PIXI.BitmapText.prototype.updateText = function()
{
    var data = PIXI.BitmapText.fonts[this.fontName];
    var pos = new PIXI.Point();
    var prevCharCode = null;
    var chars = [];
    var maxLineWidth = 0;
    var lineWidths = [];
    var line = 0;
    var scale = this.fontSize / data.size;
    for(var i = 0; i < this.text.length; i++)
    {
        var charCode = this.text.charCodeAt(i);
        if(/(?:\r\n|\r|\n)/.test(this.text.charAt(i)))
        {
            lineWidths.push(pos.x);
            maxLineWidth = Math.max(maxLineWidth, pos.x);
            line++;

            pos.x = 0;
            pos.y += data.lineHeight;
            prevCharCode = null;
            continue;
        }
        
        var charData = data.chars[charCode];
        if(!charData) continue;

        if(prevCharCode && charData[prevCharCode])
        {
           pos.x += charData.kerning[prevCharCode];
        }
        chars.push({texture:charData.texture, line: line, charCode: charCode, position: new PIXI.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)});
        pos.x += charData.xAdvance;

        prevCharCode = charCode;
    }

    lineWidths.push(pos.x);
    maxLineWidth = Math.max(maxLineWidth, pos.x);

    var lineAlignOffsets = [];
    for(i = 0; i <= line; i++)
    {
        var alignOffset = 0;
        if(this.style.align == "right")
        {
            alignOffset = maxLineWidth - lineWidths[i];
        }
        else if(this.style.align == "center")
        {
            alignOffset = (maxLineWidth - lineWidths[i]) / 2;
        }
        lineAlignOffsets.push(alignOffset);
    }

    for(i = 0; i < chars.length; i++)
    {
        var c = new PIXI.Sprite(chars[i].texture)//PIXI.Sprite.fromFrame(chars[i].charCode);
        c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale;
        c.position.y = chars[i].position.y * scale;
        c.scale.x = c.scale.y = scale;
        this.addChild(c);
    }

    this.width = pos.x * scale;
    this.height = (pos.y + data.lineHeight) * scale;
};

/**
 * Updates the transfor of this object
 *
 * @method updateTransform
 * @private
 */
PIXI.BitmapText.prototype.updateTransform = function()
{
	if(this.dirty)
	{
        while(this.children.length > 0)
        {
            this.removeChild(this.getChildAt(0));
        }
        this.updateText();

        this.dirty = false;
	}
	
	PIXI.DisplayObjectContainer.prototype.updateTransform.call(this);
};

PIXI.BitmapText.fonts = {};

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */



/**
 * The interaction manager deals with mouse and touch events. Any DisplayObject can be interactive
 * This manager also supports multitouch.
 *
 * @class InteractionManager
 * @constructor
 * @param stage {Stage} The stage to handle interactions
 */
PIXI.InteractionManager = function(stage)
{
	/**
	 * a refference to the stage
	 *
	 * @property stage
	 * @type Stage
	 */
	this.stage = stage;

	/**
	 * the mouse data
	 *
	 * @property mouse
	 * @type InteractionData
	 */
	this.mouse = new PIXI.InteractionData();

	/**
	 * an object that stores current touches (InteractionData) by id reference
	 *
	 * @property touchs
	 * @type Object
	 */
	this.touchs = {};


	
	// helpers
	this.tempPoint = new PIXI.Point();
	//this.tempMatrix =  mat3.create();

	this.mouseoverEnabled = true;

	//tiny little interactiveData pool!
	this.pool = [];

	this.interactiveItems = [];
	
	
	this.last = 0;
}

// constructor
PIXI.InteractionManager.prototype.constructor = PIXI.InteractionManager;

/**
 * Collects an interactive sprite recursively to have their interactions managed
 *
 * @method collectInteractiveSprite
 * @param displayObject {DisplayObject} the displayObject to collect
 * @param iParent {DisplayObject}
 * @private
 */
PIXI.InteractionManager.prototype.collectInteractiveSprite = function(displayObject, iParent)
{
	var children = displayObject.children;
	var length = children.length;
	
	/// make an interaction tree... {item.__interactiveParent}
	for (var i = length-1; i >= 0; i--)
	{
		var child = children[i];
		
//		if(child.visible) {
			// push all interactive bits
			if(child.interactive)
			{
				iParent.interactiveChildren = true;
				//child.__iParent = iParent;
				this.interactiveItems.push(child);

				if(child.children.length > 0)
				{
					this.collectInteractiveSprite(child, child);
				}
			}
			else
			{
				child.__iParent = null;

				if(child.children.length > 0)
				{
					this.collectInteractiveSprite(child, iParent);
				}
			}
//		}
	}
}

/**
 * Sets the target for event delegation
 *
 * @method setTarget
 * @param target {WebGLRenderer|CanvasRenderer} the renderer to bind events to
 * @private
 */
PIXI.InteractionManager.prototype.setTarget = function(target)
{
	if (window.navigator.msPointerEnabled) 
	{
		// time to remove some of that zoom in ja..
		target.view.style["-ms-content-zooming"] = "none";
    	target.view.style["-ms-touch-action"] = "none"
    
		// DO some window specific touch!
	}
	
	this.target = target;
	target.view.addEventListener('mousemove',  this.onMouseMove.bind(this), true);
	target.view.addEventListener('mousedown',  this.onMouseDown.bind(this), true);
 	document.body.addEventListener('mouseup',  this.onMouseUp.bind(this), true);
 	target.view.addEventListener('mouseout',   this.onMouseOut.bind(this), true);
	
	// aint no multi touch just yet!
	target.view.addEventListener("touchstart", this.onTouchStart.bind(this), true);
	target.view.addEventListener("touchend", this.onTouchEnd.bind(this), true);
	target.view.addEventListener("touchmove", this.onTouchMove.bind(this), true);
}

/**
 * updates the state of interactive objects
 *
 * @method update
 * @private
 */
PIXI.InteractionManager.prototype.update = function()
{
	if(!this.target)return;
	
	// frequency of 30fps??
	var now = Date.now();
	var diff = now - this.last;
	diff = (diff * 30) / 1000;
	if(diff < 1)return;
	this.last = now;
	//
	
	// ok.. so mouse events??
	// yes for now :)
	// OPTIMSE - how often to check??
	if(this.dirty)
	{
		this.dirty = false;
		
		var len = this.interactiveItems.length;
		
		for (var i=0; i < len; i++) {
		  this.interactiveItems[i].interactiveChildren = false;
		}
		
		this.interactiveItems = [];
		
		if(this.stage.interactive)this.interactiveItems.push(this.stage);
		// go through and collect all the objects that are interactive..
		this.collectInteractiveSprite(this.stage, this.stage);
	}
	
	// loop through interactive objects!
	var length = this.interactiveItems.length;
	
	this.target.view.style.cursor = "default";	
				
	for (var i = 0; i < length; i++)
	{
		var item = this.interactiveItems[i];
		
		
		//if(!item.visible)continue;
		
		// OPTIMISATION - only calculate every time if the mousemove function exists..
		// OK so.. does the object have any other interactive functions?
		// hit-test the clip!
		
		
		if(item.mouseover || item.mouseout || item.buttonMode)
		{
			// ok so there are some functions so lets hit test it..
			item.__hit = this.hitTest(item, this.mouse);
			this.mouse.target = item;
			// ok so deal with interactions..
			// loks like there was a hit!
			if(item.__hit)
			{
				if(item.buttonMode)this.target.view.style.cursor = "pointer";	
				
				if(!item.__isOver)
				{
					
					if(item.mouseover)item.mouseover(this.mouse);
					item.__isOver = true;	
				}
			}
			else
			{
				if(item.__isOver)
				{
					// roll out!
					if(item.mouseout)item.mouseout(this.mouse);
					item.__isOver = false;	
				}
			}
		}
		
		// --->
	}
}

/**
 * Is called when the mouse moves accross the renderer element
 *
 * @method onMouseMove
 * @param event {Event} The DOM event of the mouse moving
 * @private
 */
PIXI.InteractionManager.prototype.onMouseMove = function(event)
{
	this.mouse.originalEvent = event || window.event; //IE uses window.event
	// TODO optimize by not check EVERY TIME! maybe half as often? //
	var rect = this.target.view.getBoundingClientRect();
	
	this.mouse.global.x = (event.clientX - rect.left) * (this.target.width / rect.width);
	this.mouse.global.y = (event.clientY - rect.top) * ( this.target.height / rect.height);
	
	var length = this.interactiveItems.length;
	var global = this.mouse.global;
	
	
	for (var i = 0; i < length; i++)
	{
		var item = this.interactiveItems[i];
		
		if(item.mousemove)
		{
			//call the function!
			item.mousemove(this.mouse);
		}
	}
}

/**
 * Is called when the mouse button is pressed down on the renderer element
 *
 * @method onMouseDown
 * @param event {Event} The DOM event of a mouse button being pressed down
 * @private
 */
PIXI.InteractionManager.prototype.onMouseDown = function(event)
{
	this.mouse.originalEvent = event || window.event; //IE uses window.event
	
	// loop through inteaction tree...
	// hit test each item! -> 
	// get interactive items under point??
	//stage.__i
	var length = this.interactiveItems.length;
	var global = this.mouse.global;
	
	var index = 0;
	var parent = this.stage;
	
	// while 
	// hit test 
	for (var i = 0; i < length; i++)
	{
		var item = this.interactiveItems[i];
		
		if(item.mousedown || item.click)
		{
			item.__mouseIsDown = true;
			item.__hit = this.hitTest(item, this.mouse);
			
			if(item.__hit)
			{
				//call the function!
				if(item.mousedown)item.mousedown(this.mouse);
				item.__isDown = true;
				
				// just the one!
				if(!item.interactiveChildren)break;
			}
		}
	}
}


PIXI.InteractionManager.prototype.onMouseOut = function(event)
{
	var length = this.interactiveItems.length;
	
	this.target.view.style.cursor = "default";	
				
	for (var i = 0; i < length; i++)
	{
		var item = this.interactiveItems[i];
		
		if(item.__isOver)
		{
			this.mouse.target = item;
			if(item.mouseout)item.mouseout(this.mouse);
			item.__isOver = false;	
		}
	}
}

/**
 * Is called when the mouse button is released on the renderer element
 *
 * @method onMouseUp
 * @param event {Event} The DOM event of a mouse button being released
 * @private
 */
PIXI.InteractionManager.prototype.onMouseUp = function(event)
{
	this.mouse.originalEvent = event || window.event; //IE uses window.event
	
	var global = this.mouse.global;
	
	
	var length = this.interactiveItems.length;
	var up = false;
	
	for (var i = 0; i < length; i++)
	{
		var item = this.interactiveItems[i];
		
		if(item.mouseup || item.mouseupoutside || item.click)
		{
			item.__hit = this.hitTest(item, this.mouse);
			
			if(item.__hit && !up)
			{
				//call the function!
				if(item.mouseup)
				{
					item.mouseup(this.mouse);
				}
				if(item.__isDown)
				{
					if(item.click)item.click(this.mouse);
				}
				
				if(!item.interactiveChildren)up = true;
			}
			else
			{
				if(item.__isDown)
				{
					if(item.mouseupoutside)item.mouseupoutside(this.mouse);
				}
			}
		
			item.__isDown = false;	
		}
	}
}

/**
 * Tests if the current mouse coords hit a sprite
 *
 * @method hitTest
 * @param item {DisplayObject} The displayObject to test for a hit
 * @param interactionData {InteractionData} The interactiondata object to update in the case of a hit
 * @private
 */
PIXI.InteractionManager.prototype.hitTest = function(item, interactionData)
{
	var global = interactionData.global;
	
	if(item.vcount !== PIXI.visibleCount)return false;

	var isSprite = (item instanceof PIXI.Sprite),
		worldTransform = item.worldTransform,
		a00 = worldTransform[0], a01 = worldTransform[1], a02 = worldTransform[2],
		a10 = worldTransform[3], a11 = worldTransform[4], a12 = worldTransform[5],
		id = 1 / (a00 * a11 + a01 * -a10),
		x = a11 * id * global.x + -a01 * id * global.y + (a12 * a01 - a02 * a11) * id,
		y = a00 * id * global.y + -a10 * id * global.x + (-a12 * a00 + a02 * a10) * id;

	interactionData.target = item;
	
	//a sprite or display object with a hit area defined
	if(item.hitArea && item.hitArea.contains) {
		if(item.hitArea.contains(x, y)) {
			//if(isSprite)
			interactionData.target = item;

			return true;
		}
		
		return false;
	}
	// a sprite with no hitarea defined
	else if(isSprite)
	{
		var width = item.texture.frame.width,
			height = item.texture.frame.height,
			x1 = -width * item.anchor.x,
			y1;
		
		if(x > x1 && x < x1 + width)
		{
			y1 = -height * item.anchor.y;
		
			if(y > y1 && y < y1 + height)
			{
				// set the target property if a hit is true!
				interactionData.target = item
				return true;
			}
		}
	}

	var length = item.children.length;
	
	for (var i = 0; i < length; i++)
	{
		var tempItem = item.children[i];
		var hit = this.hitTest(tempItem, interactionData);
		if(hit)
		{
			// hmm.. TODO SET CORRECT TARGET?
			interactionData.target = item
			return true;
		}
	}

	return false;	
}

/**
 * Is called when a touch is moved accross the renderer element
 *
 * @method onTouchMove
 * @param event {Event} The DOM event of a touch moving accross the renderer view
 * @private
 */
PIXI.InteractionManager.prototype.onTouchMove = function(event)
{
	var rect = this.target.view.getBoundingClientRect();
	var changedTouches = event.changedTouches;
	
	for (var i=0; i < changedTouches.length; i++) 
	{
		var touchEvent = changedTouches[i];
		var touchData = this.touchs[touchEvent.identifier];
		touchData.originalEvent =  event || window.event;
		
		// update the touch position
		touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width);
		touchData.global.y = (touchEvent.clientY - rect.top)  * (this.target.height / rect.height);
	}
	
	var length = this.interactiveItems.length;
	for (var i = 0; i < length; i++)
	{
		var item = this.interactiveItems[i];
		if(item.touchmove)item.touchmove(touchData);
	}
}

/**
 * Is called when a touch is started on the renderer element
 *
 * @method onTouchStart
 * @param event {Event} The DOM event of a touch starting on the renderer view
 * @private
 */
PIXI.InteractionManager.prototype.onTouchStart = function(event)
{
	var rect = this.target.view.getBoundingClientRect();
	
	var changedTouches = event.changedTouches;
	for (var i=0; i < changedTouches.length; i++) 
	{
		var touchEvent = changedTouches[i];
		
		var touchData = this.pool.pop();
		if(!touchData)touchData = new PIXI.InteractionData();
		
		touchData.originalEvent =  event || window.event;
		
		this.touchs[touchEvent.identifier] = touchData;
		touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width);
		touchData.global.y = (touchEvent.clientY - rect.top)  * (this.target.height / rect.height);
		
		var length = this.interactiveItems.length;
		
		for (var j = 0; j < length; j++)
		{
			var item = this.interactiveItems[j];
			
			if(item.touchstart || item.tap)
			{
				item.__hit = this.hitTest(item, touchData);
				
				if(item.__hit)
				{
					//call the function!
					if(item.touchstart)item.touchstart(touchData);
					item.__isDown = true;
					item.__touchData = touchData;
					
					if(!item.interactiveChildren)break;
				}
			}
		}
	}
}

/**
 * Is called when a touch is ended on the renderer element
 *
 * @method onTouchEnd
 * @param event {Event} The DOM event of a touch ending on the renderer view
 * @private
 */
PIXI.InteractionManager.prototype.onTouchEnd = function(event)
{
	//this.mouse.originalEvent = event || window.event; //IE uses window.event
	var rect = this.target.view.getBoundingClientRect();
	var changedTouches = event.changedTouches;
	
	for (var i=0; i < changedTouches.length; i++) 
	{
		var touchEvent = changedTouches[i];
		var touchData = this.touchs[touchEvent.identifier];
		var up = false;
		touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width);
		touchData.global.y = (touchEvent.clientY - rect.top)  * (this.target.height / rect.height);
		
		var length = this.interactiveItems.length;
		for (var j = 0; j < length; j++)
		{
			var item = this.interactiveItems[j];
			var itemTouchData = item.__touchData; // <-- Here!
			item.__hit = this.hitTest(item, touchData);
		
			if(itemTouchData == touchData)
			{
				// so this one WAS down...
				touchData.originalEvent =  event || window.event;
				// hitTest??
				
				if(item.touchend || item.tap)
				{
					if(item.__hit && !up)
					{
						if(item.touchend)item.touchend(touchData);
						if(item.__isDown)
						{
							if(item.tap)item.tap(touchData);
						}
						
						if(!item.interactiveChildren)up = true;
					}
					else
					{
						if(item.__isDown)
						{
							if(item.touchendoutside)item.touchendoutside(touchData);
						}
					}
					
					item.__isDown = false;
				}
				
				item.__touchData = null;
					
			}
			else
			{
				
			}
		}
		// remove the touch..
		this.pool.push(touchData);
		this.touchs[touchEvent.identifier] = null;
	}
}

/**
 * Holds all information related to an Interaction event
 *
 * @class InteractionData
 * @constructor
 */
PIXI.InteractionData = function()
{
	/**
	 * This point stores the global coords of where the touch/mouse event happened
	 *
	 * @property global 
	 * @type Point
	 */
	this.global = new PIXI.Point();
	
	// this is here for legacy... but will remove
	this.local = new PIXI.Point();

	/**
	 * The target Sprite that was interacted with
	 *
	 * @property target
	 * @type Sprite
	 */
	this.target;

	/**
	 * When passed to an event handler, this will be the original DOM Event that was captured
	 *
	 * @property originalEvent
	 * @type Event
	 */
	this.originalEvent;
}

/**
 * This will return the local coords of the specified displayObject for this InteractionData
 *
 * @method getLocalPosition
 * @param displayObject {DisplayObject} The DisplayObject that you would like the local coords off
 * @return {Point} A point containing the coords of the InteractionData position relative to the DisplayObject
 */
PIXI.InteractionData.prototype.getLocalPosition = function(displayObject)
{
	var worldTransform = displayObject.worldTransform;
	var global = this.global;
	
	// do a cheeky transform to get the mouse coords;
	var a00 = worldTransform[0], a01 = worldTransform[1], a02 = worldTransform[2],
        a10 = worldTransform[3], a11 = worldTransform[4], a12 = worldTransform[5],
        id = 1 / (a00 * a11 + a01 * -a10);
	// set the mouse coords...
	return new PIXI.Point(a11 * id * global.x + -a01 * id * global.y + (a12 * a01 - a02 * a11) * id,
							   a00 * id * global.y + -a10 * id * global.x + (-a12 * a00 + a02 * a10) * id)
}

// constructor
PIXI.InteractionData.prototype.constructor = PIXI.InteractionData;

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * A Stage represents the root of the display tree. Everything connected to the stage is rendered
 *
 * @class Stage
 * @extends DisplayObjectContainer
 * @constructor
 * @param backgroundColor {Number} the background color of the stage, easiest way to pass this in is in hex format
 *		like: 0xFFFFFF for white
 * @param interactive {Boolean} enable / disable interaction (default is false)
 */
PIXI.Stage = function(backgroundColor, interactive)
{
	PIXI.DisplayObjectContainer.call( this );

	/**
	 * [read-only] Current transform of the object based on world (parent) factors
	 *
	 * @property worldTransform
	 * @type Mat3
	 * @readOnly
	 * @private
	 */
	this.worldTransform = PIXI.mat3.create();

	/**
	 * Whether or not the stage is interactive
	 *
	 * @property interactive
	 * @type Boolean
	 */
	this.interactive = interactive;

	/**
	 * The interaction manage for this stage, manages all interactive activity on the stage
	 *
	 * @property interactive
	 * @type InteractionManager
	 */
	this.interactionManager = new PIXI.InteractionManager(this);

	/**
	 * Whether the stage is dirty and needs to have interactions updated
	 *
	 * @property dirty
	 * @type Boolean
	 * @private
	 */
	this.dirty = true;

	this.__childrenAdded = [];
	this.__childrenRemoved = [];

	//the stage is it's own stage
	this.stage = this;

	//optimize hit detection a bit
	this.stage.hitArea = new PIXI.Rectangle(0,0,100000, 100000);

	this.setBackgroundColor(backgroundColor);
	this.worldVisible = true;
}

// constructor
PIXI.Stage.prototype = Object.create( PIXI.DisplayObjectContainer.prototype );
PIXI.Stage.prototype.constructor = PIXI.Stage;

/*
 * Updates the object transform for rendering
 *
 * @method updateTransform
 * @private
 */
PIXI.Stage.prototype.updateTransform = function()
{
	this.worldAlpha = 1;		
	this.vcount = PIXI.visibleCount;
	
	for(var i=0,j=this.children.length; i<j; i++)
	{
		this.children[i].updateTransform();	
	}
	
	if(this.dirty)
	{
		this.dirty = false;
		// update interactive!
		this.interactionManager.dirty = true;
	}
	
	
	if(this.interactive)this.interactionManager.update();
}

/**
 * Sets the background color for the stage
 *
 * @method setBackgroundColor
 * @param backgroundColor {Number} the color of the background, easiest way to pass this in is in hex format
 *		like: 0xFFFFFF for white
 */
PIXI.Stage.prototype.setBackgroundColor = function(backgroundColor)
{
	this.backgroundColor = backgroundColor || 0x000000;
	this.backgroundColorSplit = HEXtoRGB(this.backgroundColor);
	var hex = this.backgroundColor.toString(16);
	hex = "000000".substr(0, 6 - hex.length) + hex;
	this.backgroundColorString = "#" + hex;
}

/**
 * This will return the point containing global coords of the mouse.
 *
 * @method getMousePosition
 * @return {Point} The point containing the coords of the global InteractionData position.
 */
PIXI.Stage.prototype.getMousePosition = function()
{
	return this.interactionManager.mouse.global;
}

// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating

// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel

// MIT license

/**
 * A polyfill for requestAnimationFrame
 *
 * @method requestAnimationFrame
 */
/**
 * A polyfill for cancelAnimationFrame
 *
 * @method cancelAnimationFrame
 */
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 
                               || window[vendors[x]+'CancelRequestAnimationFrame'];
}

if (!window.requestAnimationFrame)
    window.requestAnimationFrame = function(callback, element) {
        var currTime = new Date().getTime();
        var timeToCall = Math.max(0, 16 - (currTime - lastTime));
        var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
          timeToCall);
        lastTime = currTime + timeToCall;
        return id;
    };

if (!window.cancelAnimationFrame)
    window.cancelAnimationFrame = function(id) {
        clearTimeout(id);
    };

window.requestAnimFrame = window.requestAnimationFrame;

/**
 * Converts a hex color number to an [R, G, B] array
 *
 * @method HEXtoRGB
 * @param hex {Number}
 */
function HEXtoRGB(hex) {
	return [(hex >> 16 & 0xFF) / 255, ( hex >> 8 & 0xFF) / 255, (hex & 0xFF)/ 255];
}

/**
 * A polyfill for Function.prototype.bind
 *
 * @method bind
 */
if (typeof Function.prototype.bind != 'function') {
  Function.prototype.bind = (function () {
    var slice = Array.prototype.slice;
    return function (thisArg) {
      var target = this, boundArgs = slice.call(arguments, 1);
 
      if (typeof target != 'function') throw new TypeError();
 
      function bound() {
	var args = boundArgs.concat(slice.call(arguments));
	target.apply(this instanceof bound ? this : thisArg, args);
      }
 
      bound.prototype = (function F(proto) {
          proto && (F.prototype = proto);
          if (!(this instanceof F)) return new F;          
	})(target.prototype);
 
      return bound;
    };
  })();
}

/**
 * A wrapper for ajax requests to be handled cross browser
 *
 * @class AjaxRequest
 * @constructor
 */
var AjaxRequest = PIXI.AjaxRequest = function()
{
	var activexmodes = ["Msxml2.XMLHTTP", "Microsoft.XMLHTTP"] //activeX versions to check for in IE
	
	if (window.ActiveXObject)
	{ //Test for support for ActiveXObject in IE first (as XMLHttpRequest in IE7 is broken)
		for (var i=0; i<activexmodes.length; i++)
		{
			try{
				return new ActiveXObject(activexmodes[i])
			}
   			catch(e){
    			//suppress error
   			}
		}
	}
	else if (window.XMLHttpRequest) // if Mozilla, Safari etc
  	{
  		return new XMLHttpRequest()
 	}
 	else
 	{
		return false;
 	}
}

/*
 * DEBUGGING ONLY
 */
PIXI.runList = function(item)
{
	console.log(">>>>>>>>>")
	console.log("_")
	var safe = 0;
	var tmp = item.first;
	console.log(tmp);
	
	while(tmp._iNext)
	{
		safe++;
		tmp = tmp._iNext;
		console.log(tmp);
	//	console.log(tmp);
	
		if(safe > 100)
		{
			console.log("BREAK")
			break
		}
	}	
}






/**
 * https://github.com/mrdoob/eventtarget.js/
 * THankS mr DOob!
 */

/**
 * Adds event emitter functionality to a class
 *
 * @class EventTarget
 * @example
 *		function MyEmitter() {
 *			PIXI.EventTarget.call(this); //mixes in event target stuff
 *		}
 *
 *		var em = new MyEmitter();
 *		em.emit({ type: 'eventName', data: 'some data' });
 */
PIXI.EventTarget = function () {

	var listeners = {};
	
	this.addEventListener = this.on = function ( type, listener ) {
		
		
		if ( listeners[ type ] === undefined ) {

			listeners[ type ] = [];
			
		}

		if ( listeners[ type ].indexOf( listener ) === - 1 ) {

			listeners[ type ].push( listener );
		}

	};

	this.dispatchEvent = this.emit = function ( event ) {
		
		for ( var listener in listeners[ event.type ] ) {

			listeners[ event.type ][ listener ]( event );
			
		}

	};

	this.removeEventListener = this.off = function ( type, listener ) {

		var index = listeners[ type ].indexOf( listener );

		if ( index !== - 1 ) {

			listeners[ type ].splice( index, 1 );

		}

	};

};

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * This helper function will automatically detect which renderer you should be using.
 * WebGL is the preferred renderer as it is a lot fastest. If webGL is not supported by
 * the browser then this function will return a canvas renderer
 *
 * @method autoDetectRenderer
 * @static
 * @param width {Number} the width of the renderers view
 * @param height {Number} the height of the renderers view
 * @param view {Canvas} the canvas to use as a view, optional
 * @param transparent=false {Boolean} the transparency of the render view, default false
 * @param antialias=false {Boolean} sets antialias (only applicable in webGL chrome at the moment)
 * 
 * antialias
 */
PIXI.autoDetectRenderer = function(width, height, view, transparent, antialias)
{
	if(!width)width = 800;
	if(!height)height = 600;

	// BORROWED from Mr Doob (mrdoob.com)
	var webgl = ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )();

	//console.log(webgl);
	if( webgl )
	{
		return new PIXI.WebGLRenderer(width, height, view, transparent, antialias);
	}

	return	new PIXI.CanvasRenderer(width, height, view, transparent);
};



/*
	PolyK library
	url: http://polyk.ivank.net
	Released under MIT licence.
	
	Copyright (c) 2012 Ivan Kuckir

	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.

	This is an amazing lib! 
	
	slightly modified by mat groves (matgroves.com);
*/

PIXI.PolyK = {};

/**
 * Triangulates shapes for webGL graphic fills
 *
 * @method Triangulate
 * @namespace PolyK
 * @constructor
 */
PIXI.PolyK.Triangulate = function(p)
{
	var sign = true;
	
	var n = p.length>>1;
	if(n<3) return [];
	var tgs = [];
	var avl = [];
	for(var i=0; i<n; i++) avl.push(i);
	
	var i = 0;
	var al = n;
	while(al > 3)
	{
		var i0 = avl[(i+0)%al];
		var i1 = avl[(i+1)%al];
		var i2 = avl[(i+2)%al];
		
		var ax = p[2*i0],  ay = p[2*i0+1];
		var bx = p[2*i1],  by = p[2*i1+1];
		var cx = p[2*i2],  cy = p[2*i2+1];
		
		var earFound = false;
		if(PIXI.PolyK._convex(ax, ay, bx, by, cx, cy, sign))
		{
			earFound = true;
			for(var j=0; j<al; j++)
			{
				var vi = avl[j];
				if(vi==i0 || vi==i1 || vi==i2) continue;
				if(PIXI.PolyK._PointInTriangle(p[2*vi], p[2*vi+1], ax, ay, bx, by, cx, cy)) {earFound = false; break;}
			}
		}
		if(earFound)
		{
			tgs.push(i0, i1, i2);
			avl.splice((i+1)%al, 1);
			al--;
			i = 0;
		}
		else if(i++ > 3*al) 
		{
			// need to flip flip reverse it!
			// reset!
			if(sign)
			{
				var tgs = [];
				avl = [];
				for(var i=0; i<n; i++) avl.push(i);
				
				i = 0;
				al = n;
				
				sign = false;
			}
			else
			{
				console.log("PIXI Warning: shape too complex to fill")
				return [];
			}				
		}
	}
	tgs.push(avl[0], avl[1], avl[2]);
	return tgs;
}

/**
 * Checks if a point is within a triangle
 *
 * @class _PointInTriangle
 * @namespace PolyK
 * @private
 */
PIXI.PolyK._PointInTriangle = function(px, py, ax, ay, bx, by, cx, cy)
{
	var v0x = cx-ax;
	var v0y = cy-ay;
	var v1x = bx-ax;
	var v1y = by-ay;
	var v2x = px-ax;
	var v2y = py-ay;
	
	var dot00 = v0x*v0x+v0y*v0y;
	var dot01 = v0x*v1x+v0y*v1y;
	var dot02 = v0x*v2x+v0y*v2y;
	var dot11 = v1x*v1x+v1y*v1y;
	var dot12 = v1x*v2x+v1y*v2y;
	
	var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
	var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
	var v = (dot00 * dot12 - dot01 * dot02) * invDenom;

	// Check if point is in triangle
	return (u >= 0) && (v >= 0) && (u + v < 1);
}

/**
 * Checks if a shape is convex
 *
 * @class _convex
 * @namespace PolyK
 * @private
 */
PIXI.PolyK._convex = function(ax, ay, bx, by, cx, cy, sign)
{
	return ((ay-by)*(cx-bx) + (bx-ax)*(cy-by) >= 0) == sign;
}


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */


/*
 * the default suoer fast shader!
 */

PIXI.shaderFragmentSrc = [
  "precision mediump float;",
  "varying vec2 vTextureCoord;",
  "varying float vColor;",
  "uniform sampler2D uSampler;",
  "void main(void) {",
    "gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y));",
    "gl_FragColor = gl_FragColor * vColor;",
  "}"
];

PIXI.shaderVertexSrc = [
  "attribute vec2 aVertexPosition;",
  "attribute vec2 aTextureCoord;",
  "attribute float aColor;",
  //"uniform mat4 uMVMatrix;",
  
  "uniform vec2 projectionVector;",
  "varying vec2 vTextureCoord;",
  "varying float vColor;",
  "void main(void) {",
   // "gl_Position = uMVMatrix * vec4(aVertexPosition, 1.0, 1.0);",
    "gl_Position = vec4( aVertexPosition.x / projectionVector.x -1.0, aVertexPosition.y / -projectionVector.y + 1.0 , 0.0, 1.0);",
    "vTextureCoord = aTextureCoord;",
    "vColor = aColor;",
  "}"
];

/*
 * the triangle strip shader..
 */

PIXI.stripShaderFragmentSrc = [
  "precision mediump float;",
  "varying vec2 vTextureCoord;",
  "varying float vColor;",
  "uniform float alpha;",
  "uniform sampler2D uSampler;",
  "void main(void) {",
    "gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y));",
    "gl_FragColor = gl_FragColor * alpha;",
  "}"
];


PIXI.stripShaderVertexSrc = [
  "attribute vec2 aVertexPosition;",
  "attribute vec2 aTextureCoord;",
  "attribute float aColor;",
  "uniform mat3 translationMatrix;",
  "uniform vec2 projectionVector;",
  "varying vec2 vTextureCoord;",
  "varying float vColor;",
  "void main(void) {",
	"vec3 v = translationMatrix * vec3(aVertexPosition, 1.0);",
    "gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);",
    "vTextureCoord = aTextureCoord;",
    "vColor = aColor;",
  "}"
];


/*
 * primitive shader..
 */

PIXI.primitiveShaderFragmentSrc = [
  "precision mediump float;",
  "varying vec4 vColor;",
  "void main(void) {",
    "gl_FragColor = vColor;",
  "}"
];

PIXI.primitiveShaderVertexSrc = [
  "attribute vec2 aVertexPosition;",
  "attribute vec4 aColor;",
  "uniform mat3 translationMatrix;",
  "uniform vec2 projectionVector;",
  "uniform float alpha;",
  "varying vec4 vColor;",
  "void main(void) {",
  	"vec3 v = translationMatrix * vec3(aVertexPosition, 1.0);",
    "gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);",
    "vColor = aColor  * alpha;",
  "}"
];

PIXI.initPrimitiveShader = function() 
{
	var gl = PIXI.gl;

	var shaderProgram = PIXI.compileProgram(PIXI.primitiveShaderVertexSrc, PIXI.primitiveShaderFragmentSrc)
	
    gl.useProgram(shaderProgram);

    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    shaderProgram.colorAttribute = gl.getAttribLocation(shaderProgram, "aColor");
    
    shaderProgram.projectionVector = gl.getUniformLocation(shaderProgram, "projectionVector");
    shaderProgram.translationMatrix = gl.getUniformLocation(shaderProgram, "translationMatrix");
    
	shaderProgram.alpha = gl.getUniformLocation(shaderProgram, "alpha");

	PIXI.primitiveProgram = shaderProgram;
}

PIXI.initDefaultShader = function() 
{
	var gl = this.gl;
	var shaderProgram = PIXI.compileProgram(PIXI.shaderVertexSrc, PIXI.shaderFragmentSrc)
	
    gl.useProgram(shaderProgram);

    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    shaderProgram.projectionVector = gl.getUniformLocation(shaderProgram, "projectionVector");
    shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
	shaderProgram.colorAttribute = gl.getAttribLocation(shaderProgram, "aColor");

   // shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
    
	PIXI.shaderProgram = shaderProgram;
}

PIXI.initDefaultStripShader = function() 
{
	var gl = this.gl;
	var shaderProgram = PIXI.compileProgram(PIXI.stripShaderVertexSrc, PIXI.stripShaderFragmentSrc)
	
    gl.useProgram(shaderProgram);

    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    shaderProgram.projectionVector = gl.getUniformLocation(shaderProgram, "projectionVector");
    shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
	shaderProgram.translationMatrix = gl.getUniformLocation(shaderProgram, "translationMatrix");
	shaderProgram.alpha = gl.getUniformLocation(shaderProgram, "alpha");

	shaderProgram.colorAttribute = gl.getAttribLocation(shaderProgram, "aColor");

    shaderProgram.projectionVector = gl.getUniformLocation(shaderProgram, "projectionVector");
    
    shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
    
	PIXI.stripShaderProgram = shaderProgram;
}

PIXI.CompileVertexShader = function(gl, shaderSrc)
{
  return PIXI._CompileShader(gl, shaderSrc, gl.VERTEX_SHADER);
}

PIXI.CompileFragmentShader = function(gl, shaderSrc)
{
  return PIXI._CompileShader(gl, shaderSrc, gl.FRAGMENT_SHADER);
}

PIXI._CompileShader = function(gl, shaderSrc, shaderType)
{
  var src = shaderSrc.join("\n");
  var shader = gl.createShader(shaderType);
  gl.shaderSource(shader, src);
  gl.compileShader(shader);

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    alert(gl.getShaderInfoLog(shader));
    return null;
  }

  return shader;
}


PIXI.compileProgram = function(vertexSrc, fragmentSrc)
{
	var gl = PIXI.gl;
	var fragmentShader = PIXI.CompileFragmentShader(gl, fragmentSrc);
	var vertexShader = PIXI.CompileVertexShader(gl, vertexSrc);
	
	var shaderProgram = gl.createProgram();
	
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert("Could not initialise shaders");
    }

	return shaderProgram;
} 


PIXI.activateDefaultShader = function()
{
	var gl = PIXI.gl;
	var shaderProgram = PIXI.shaderProgram;
	
	gl.useProgram(shaderProgram);
	
	
	gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
    gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
    gl.enableVertexAttribArray(shaderProgram.colorAttribute);
}

	

PIXI.activatePrimitiveShader = function()
{
	var gl = PIXI.gl;
	
	gl.disableVertexAttribArray(PIXI.shaderProgram.textureCoordAttribute);
    gl.disableVertexAttribArray(PIXI.shaderProgram.colorAttribute);
    
	gl.useProgram(PIXI.primitiveProgram);
	
	gl.enableVertexAttribArray(PIXI.primitiveProgram.vertexPositionAttribute);
	gl.enableVertexAttribArray(PIXI.primitiveProgram.colorAttribute);
} 


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * A set of functions used by the webGL renderer to draw the primitive graphics data
 *
 * @class CanvasGraphics
 */
PIXI.WebGLGraphics = function()
{
	
}

/**
 * Renders the graphics object
 *
 * @static
 * @private
 * @method renderGraphics
 * @param graphics {Graphics}
 * @param projection {Object}
 */
PIXI.WebGLGraphics.renderGraphics = function(graphics, projection)
{
	var gl = PIXI.gl;
	
	if(!graphics._webGL)graphics._webGL = {points:[], indices:[], lastIndex:0, 
										   buffer:gl.createBuffer(),
										   indexBuffer:gl.createBuffer()};
	
	if(graphics.dirty)
	{
		graphics.dirty = false;
		
		if(graphics.clearDirty)
		{
			graphics.clearDirty = false;
			
			graphics._webGL.lastIndex = 0;
			graphics._webGL.points = [];
			graphics._webGL.indices = [];
			
		}
		
		PIXI.WebGLGraphics.updateGraphics(graphics);
	}
	
	
	PIXI.activatePrimitiveShader();
	
	// This  could be speeded up fo sure!
	var m = PIXI.mat3.clone(graphics.worldTransform);
	
	PIXI.mat3.transpose(m);
	
	// set the matrix transform for the 
 	gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
 	
 	gl.uniformMatrix3fv(PIXI.primitiveProgram.translationMatrix, false, m);
 	
	gl.uniform2f(PIXI.primitiveProgram.projectionVector, projection.x, projection.y);
	
	gl.uniform1f(PIXI.primitiveProgram.alpha, graphics.worldAlpha);

	gl.bindBuffer(gl.ARRAY_BUFFER, graphics._webGL.buffer);
	
	// WHY DOES THIS LINE NEED TO BE THERE???
	gl.vertexAttribPointer(PIXI.shaderProgram.vertexPositionAttribute, 2, gl.FLOAT, false, 0, 0);
	// its not even used.. but need to be set or it breaks?
	// only on pc though..
	
	gl.vertexAttribPointer(PIXI.primitiveProgram.vertexPositionAttribute, 2, gl.FLOAT, false, 4 * 6, 0);
	gl.vertexAttribPointer(PIXI.primitiveProgram.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4);
	
	// set the index buffer!
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, graphics._webGL.indexBuffer);
	
	gl.drawElements(gl.TRIANGLE_STRIP,  graphics._webGL.indices.length, gl.UNSIGNED_SHORT, 0 );
	
	// return to default shader...
	PIXI.activateDefaultShader();
}

/**
 * Updates the graphics object
 *
 * @static
 * @private
 * @method updateGraphics
 * @param graphics {Graphics}
 */
PIXI.WebGLGraphics.updateGraphics = function(graphics)
{
	for (var i=graphics._webGL.lastIndex; i < graphics.graphicsData.length; i++) 
	{
		var data = graphics.graphicsData[i];
		
		if(data.type == PIXI.Graphics.POLY)
		{
			if(data.fill)
			{
				if(data.points.length>3) 
				PIXI.WebGLGraphics.buildPoly(data, graphics._webGL);
			}
			
			if(data.lineWidth > 0)
			{
				PIXI.WebGLGraphics.buildLine(data, graphics._webGL);
			}
		}
		else if(data.type == PIXI.Graphics.RECT)
		{
			PIXI.WebGLGraphics.buildRectangle(data, graphics._webGL);
		}
		else if(data.type == PIXI.Graphics.CIRC || data.type == PIXI.Graphics.ELIP)
		{
			PIXI.WebGLGraphics.buildCircle(data, graphics._webGL);
		}
	};
	
	graphics._webGL.lastIndex = graphics.graphicsData.length;
	
	var gl = PIXI.gl;

	graphics._webGL.glPoints = new Float32Array(graphics._webGL.points);
	
	gl.bindBuffer(gl.ARRAY_BUFFER, graphics._webGL.buffer);
	gl.bufferData(gl.ARRAY_BUFFER, graphics._webGL.glPoints, gl.STATIC_DRAW);
	
	graphics._webGL.glIndicies = new Uint16Array(graphics._webGL.indices);
	
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, graphics._webGL.indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, graphics._webGL.glIndicies, gl.STATIC_DRAW);
}

/**
 * Builds a rectangle to draw
 *
 * @static
 * @private
 * @method buildRectangle
 * @param graphics {Graphics}
 * @param webGLData {Object}
 */
PIXI.WebGLGraphics.buildRectangle = function(graphicsData, webGLData)
{
	// --- //
	// need to convert points to a nice regular data
	// 
	var rectData = graphicsData.points;
	var x = rectData[0];
	var y = rectData[1];
	var width = rectData[2];
	var height = rectData[3];
	
	
	if(graphicsData.fill)
	{
		var color = HEXtoRGB(graphicsData.fillColor);
		var alpha = graphicsData.fillAlpha;
		
		var r = color[0] * alpha;
		var g = color[1] * alpha;
		var b = color[2] * alpha;
	
		var verts = webGLData.points;
		var indices = webGLData.indices;
	
		var vertPos = verts.length/6;
		
		// start
		verts.push(x, y);
		verts.push(r, g, b, alpha);
		
		verts.push(x + width, y);
		verts.push(r, g, b, alpha);
		
		verts.push(x , y + height);
		verts.push(r, g, b, alpha);
		
		verts.push(x + width, y + height);
		verts.push(r, g, b, alpha);
		
		// insert 2 dead triangles..
		indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3)
	}
	
	if(graphicsData.lineWidth)
	{
		graphicsData.points = [x, y,
				  x + width, y,
				  x + width, y + height,
				  x, y + height,
				  x, y];
	
		PIXI.WebGLGraphics.buildLine(graphicsData, webGLData);
	}
	
}

/**
 * Builds a circle to draw
 *
 * @static
 * @private
 * @method buildCircle
 * @param graphics {Graphics}
 * @param webGLData {Object}
 */
PIXI.WebGLGraphics.buildCircle = function(graphicsData, webGLData)
{
	// --- //
	// need to convert points to a nice regular data
	// 
	var rectData = graphicsData.points;
	var x = rectData[0];
	var y = rectData[1];
	var width = rectData[2];
	var height = rectData[3];
	
	var totalSegs = 40;
	var seg = (Math.PI * 2) / totalSegs ;
		
	if(graphicsData.fill)
	{
		var color = HEXtoRGB(graphicsData.fillColor);
		var alpha = graphicsData.fillAlpha;

		var r = color[0] * alpha;
		var g = color[1] * alpha;
		var b = color[2] * alpha;
	
		var verts = webGLData.points;
		var indices = webGLData.indices;
	
		var vecPos = verts.length/6;
		
		indices.push(vecPos);
		
		for (var i=0; i < totalSegs + 1 ; i++) 
		{
			verts.push(x,y, r, g, b, alpha);
			
			verts.push(x + Math.sin(seg * i) * width,
					   y + Math.cos(seg * i) * height,
					   r, g, b, alpha);
		
			indices.push(vecPos++, vecPos++);
		};
		
		indices.push(vecPos-1);
	}
	
	if(graphicsData.lineWidth)
	{
		graphicsData.points = [];
		
		for (var i=0; i < totalSegs + 1; i++) 
		{
			graphicsData.points.push(x + Math.sin(seg * i) * width,
									 y + Math.cos(seg * i) * height)
		};
		
		PIXI.WebGLGraphics.buildLine(graphicsData, webGLData);
	}
	
}

/**
 * Builds a line to draw
 *
 * @static
 * @private
 * @method buildLine
 * @param graphics {Graphics}
 * @param webGLData {Object}
 */
PIXI.WebGLGraphics.buildLine = function(graphicsData, webGLData)
{
	// TODO OPTIMISE!
	
	var wrap = true;
	var points = graphicsData.points;
	if(points.length == 0)return;
	
	// get first and last point.. figure out the middle!
	var firstPoint = new PIXI.Point( points[0], points[1] );
	var lastPoint = new PIXI.Point( points[points.length - 2], points[points.length - 1] );
	
	// if the first point is the last point - goona have issues :)
	if(firstPoint.x == lastPoint.x && firstPoint.y == lastPoint.y)
	{
		points.pop();
		points.pop();
		
		lastPoint = new PIXI.Point( points[points.length - 2], points[points.length - 1] );
		
		var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5;
		var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5;
		
		points.unshift(midPointX, midPointY);
		points.push(midPointX, midPointY)
	}
	
	var verts = webGLData.points;
	var indices = webGLData.indices;
	var length = points.length / 2;
	var indexCount = points.length;
	var indexStart = verts.length/6;
	
	// DRAW the Line
	var width = graphicsData.lineWidth / 2;
	
	// sort color
	var color = HEXtoRGB(graphicsData.lineColor);
	var alpha = graphicsData.lineAlpha;
	var r = color[0] * alpha;
	var g = color[1] * alpha;
	var b = color[2] * alpha;
	
	var p1x, p1y, p2x, p2y, p3x, p3y;
	var perpx, perpy, perp2x, perp2y, perp3x, perp3y;
	var ipx, ipy;
	var a1, b1, c1, a2, b2, c2;
	var denom, pdist, dist;
	
	p1x = points[0];
	p1y = points[1];
	
	p2x = points[2];
	p2y = points[3];
	
	perpx = -(p1y - p2y);
	perpy =  p1x - p2x;
	
	dist = Math.sqrt(perpx*perpx + perpy*perpy);
	
	perpx /= dist;
	perpy /= dist;
	perpx *= width;
	perpy *= width;
	
	// start
	verts.push(p1x - perpx , p1y - perpy,
				r, g, b, alpha);
	
	verts.push(p1x + perpx , p1y + perpy,
				r, g, b, alpha);
	
	for (var i = 1; i < length-1; i++) 
	{
		p1x = points[(i-1)*2];
		p1y = points[(i-1)*2 + 1];
		
		p2x = points[(i)*2]
		p2y = points[(i)*2 + 1]
		
		p3x = points[(i+1)*2];
		p3y = points[(i+1)*2 + 1];
		
		perpx = -(p1y - p2y);
		perpy = p1x - p2x;
		
		dist = Math.sqrt(perpx*perpx + perpy*perpy);
		perpx /= dist;
		perpy /= dist;
		perpx *= width;
		perpy *= width;

		perp2x = -(p2y - p3y);
		perp2y = p2x - p3x;
		
		dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y);
		perp2x /= dist;
		perp2y /= dist;
		perp2x *= width;
		perp2y *= width;
		
		a1 = (-perpy + p1y) - (-perpy + p2y);
	    b1 = (-perpx + p2x) - (-perpx + p1x);
	    c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y);
	    a2 = (-perp2y + p3y) - (-perp2y + p2y);
	    b2 = (-perp2x + p2x) - (-perp2x + p3x);
	    c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y);
	 
	    denom = a1*b2 - a2*b1;
	    
	    if (denom == 0) {
	    	denom+=1;
	    }
	    
	    px = (b1*c2 - b2*c1)/denom;
	    py = (a2*c1 - a1*c2)/denom;
		
		pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y);
		
		if(pdist > 140 * 140)
		{
			perp3x = perpx - perp2x;
			perp3y = perpy - perp2y;
			
			dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y);
			perp3x /= dist;
			perp3y /= dist;
			perp3x *= width;
			perp3y *= width;
			
			verts.push(p2x - perp3x, p2y -perp3y);
			verts.push(r, g, b, alpha);
			
			verts.push(p2x + perp3x, p2y +perp3y);
			verts.push(r, g, b, alpha);
			
			verts.push(p2x - perp3x, p2y -perp3y);
			verts.push(r, g, b, alpha);
			
			indexCount++;
		}
		else
		{
			verts.push(px , py);
			verts.push(r, g, b, alpha);
			
			verts.push(p2x - (px-p2x), p2y - (py - p2y));
			verts.push(r, g, b, alpha);
		}
	}
	
	p1x = points[(length-2)*2]
	p1y = points[(length-2)*2 + 1] 
	
	p2x = points[(length-1)*2]
	p2y = points[(length-1)*2 + 1]
	
	perpx = -(p1y - p2y)
	perpy = p1x - p2x;
	
	dist = Math.sqrt(perpx*perpx + perpy*perpy);
	perpx /= dist;
	perpy /= dist;
	perpx *= width;
	perpy *= width;
	
	verts.push(p2x - perpx , p2y - perpy)
	verts.push(r, g, b, alpha);
	
	verts.push(p2x + perpx , p2y + perpy)
	verts.push(r, g, b, alpha);
	
	indices.push(indexStart);
	
	for (var i=0; i < indexCount; i++) 
	{
		indices.push(indexStart++);
	};
	
	indices.push(indexStart-1);
}

/**
 * Builds a polygon to draw
 *
 * @static
 * @private
 * @method buildPoly
 * @param graphics {Graphics}
 * @param webGLData {Object}
 */
PIXI.WebGLGraphics.buildPoly = function(graphicsData, webGLData)
{
	var points = graphicsData.points;
	if(points.length < 6)return;
	
	// get first and last point.. figure out the middle!
	var verts = webGLData.points;
	var indices = webGLData.indices;
	
	var length = points.length / 2;
	
	// sort color
	var color = HEXtoRGB(graphicsData.fillColor);
	var alpha = graphicsData.fillAlpha;
	var r = color[0] * alpha;
	var g = color[1] * alpha;
	var b = color[2] * alpha;
	
	var triangles = PIXI.PolyK.Triangulate(points);
	
	var vertPos = verts.length / 6;
	
	for (var i=0; i < triangles.length; i+=3) 
	{
		indices.push(triangles[i] + vertPos);
		indices.push(triangles[i] + vertPos);
		indices.push(triangles[i+1] + vertPos);
		indices.push(triangles[i+2] +vertPos);
		indices.push(triangles[i+2] + vertPos);
	};
	
	for (var i = 0; i < length; i++) 
	{
		verts.push(points[i * 2], points[i * 2 + 1],
				   r, g, b, alpha);
	};
}

function HEXtoRGB(hex) {
	return [(hex >> 16 & 0xFF) / 255, ( hex >> 8 & 0xFF) / 255, (hex & 0xFF)/ 255];
}





/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

PIXI._defaultFrame = new PIXI.Rectangle(0,0,1,1);

// an instance of the gl context..
// only one at the moment :/
PIXI.gl;

/**
 * the WebGLRenderer is draws the stage and all its content onto a webGL enabled canvas. This renderer
 * should be used for browsers support webGL. This Render works by automatically managing webGLBatchs.
 * So no need for Sprite Batch's or Sprite Cloud's
 * Dont forget to add the view to your DOM or you will not see anything :)
 *
 * @class WebGLRenderer
 * @constructor
 * @param width=0 {Number} the width of the canvas view
 * @param height=0 {Number} the height of the canvas view
 * @param view {Canvas} the canvas to use as a view, optional
 * @param transparent=false {Boolean} the transparency of the render view, default false
 * @param antialias=false {Boolean} sets antialias (only applicable in chrome at the moment)
 * 
 */
PIXI.WebGLRenderer = function(width, height, view, transparent, antialias)
{
	// do a catch.. only 1 webGL renderer..

	this.transparent = !!transparent;

	this.width = width || 800;
	this.height = height || 600;

	this.view = view || document.createElement( 'canvas' ); 
    this.view.width = this.width;
	this.view.height = this.height;

	// deal with losing context..	
    var scope = this;
	this.view.addEventListener('webglcontextlost', function(event) { scope.handleContextLost(event); }, false)
	this.view.addEventListener('webglcontextrestored', function(event) { scope.handleContextRestored(event); }, false)

	this.batchs = [];

	try 
 	{
        PIXI.gl = this.gl = this.view.getContext("experimental-webgl",  {  	
    		 alpha: this.transparent,
    		 antialias:!!antialias, // SPEED UP??
    		 premultipliedAlpha:true,
    		 stencil:true
        });
    } 
    catch (e) 
    {
    	throw new Error(" This browser does not support webGL. Try using the canvas renderer" + this);
    }

    PIXI.initPrimitiveShader();
    PIXI.initDefaultShader();
    PIXI.initDefaultStripShader();

    PIXI.activateDefaultShader();

    var gl = this.gl;
    PIXI.WebGLRenderer.gl = gl;

    this.batch = new PIXI.WebGLBatch(gl);
   	gl.disable(gl.DEPTH_TEST);
   	gl.disable(gl.CULL_FACE);

    gl.enable(gl.BLEND);
    gl.colorMask(true, true, true, this.transparent); 

    PIXI.projection = new PIXI.Point(400, 300);

    this.resize(this.width, this.height);
    this.contextLost = false;

    this.stageRenderGroup = new PIXI.WebGLRenderGroup(this.gl);
}

// constructor
PIXI.WebGLRenderer.prototype.constructor = PIXI.WebGLRenderer;

/**
 * Gets a new WebGLBatch from the pool
 *
 * @static
 * @method getBatch
 * @return {WebGLBatch}
 * @private 
 */
PIXI.WebGLRenderer.getBatch = function()
{
	if(PIXI._batchs.length == 0)
	{
		return new PIXI.WebGLBatch(PIXI.WebGLRenderer.gl);
	}
	else
	{
		return PIXI._batchs.pop();
	}
}

/**
 * Puts a batch back into the pool
 *
 * @static
 * @method returnBatch
 * @param batch {WebGLBatch} The batch to return
 * @private
 */
PIXI.WebGLRenderer.returnBatch = function(batch)
{
	batch.clean();	
	PIXI._batchs.push(batch);
}

/**
 * Renders the stage to its webGL view
 *
 * @method render
 * @param stage {Stage} the Stage element to be rendered
 */
PIXI.WebGLRenderer.prototype.render = function(stage)
{
	if(this.contextLost)return;
	
	
	// if rendering a new stage clear the batchs..
	if(this.__stage !== stage)
	{
		// TODO make this work
		// dont think this is needed any more?
		this.__stage = stage;
		this.stageRenderGroup.setRenderable(stage);
	}
	
	// TODO not needed now... 
	// update children if need be
	// best to remove first!
	/*for (var i=0; i < stage.__childrenRemoved.length; i++)
	{
		var group = stage.__childrenRemoved[i].__renderGroup
		if(group)group.removeDisplayObject(stage.__childrenRemoved[i]);
	}*/

	// update any textures	
	PIXI.WebGLRenderer.updateTextures();
		
	// update the scene graph	
	PIXI.visibleCount++;
	stage.updateTransform();
	
	var gl = this.gl;
	
	// -- Does this need to be set every frame? -- //
	gl.colorMask(true, true, true, this.transparent); 
	gl.viewport(0, 0, this.width, this.height);	
	
   	gl.bindFramebuffer(gl.FRAMEBUFFER, null);

	if (!this.transparent) {
		gl.clearColor(stage.backgroundColorSplit[0],stage.backgroundColorSplit[1],stage.backgroundColorSplit[2], 1);
	} else {
		gl.clearColor(0,0,0,0);
	}
	gl.clear(gl.COLOR_BUFFER_BIT);

	// HACK TO TEST
	
	this.stageRenderGroup.backgroundColor = stage.backgroundColorSplit;
	this.stageRenderGroup.render(PIXI.projection);
	
	// interaction
	// run interaction!
	if(stage.interactive)
	{
		//need to add some events!
		if(!stage._interactiveEventsAdded)
		{
			stage._interactiveEventsAdded = true;
			stage.interactionManager.setTarget(this);
		}
	}
	
	// after rendering lets confirm all frames that have been uodated..
	if(PIXI.Texture.frameUpdates.length > 0)
	{
		for (var i=0; i < PIXI.Texture.frameUpdates.length; i++) 
		{
		  	PIXI.Texture.frameUpdates[i].updateFrame = false;
		};
		
		PIXI.Texture.frameUpdates = [];
	}
}

/**
 * Updates the textures loaded into this webgl renderer
 *
 * @static
 * @method updateTextures
 * @private
 */
PIXI.WebGLRenderer.updateTextures = function()
{
	//TODO break this out into a texture manager...
	for (var i=0; i < PIXI.texturesToUpdate.length; i++) PIXI.WebGLRenderer.updateTexture(PIXI.texturesToUpdate[i]);
	for (var i=0; i < PIXI.texturesToDestroy.length; i++) PIXI.WebGLRenderer.destroyTexture(PIXI.texturesToDestroy[i]);
	PIXI.texturesToUpdate = [];
	PIXI.texturesToDestroy = [];
}

/**
 * Updates a loaded webgl texture
 *
 * @static
 * @method updateTexture
 * @param texture {Texture} The texture to update
 * @private
 */
PIXI.WebGLRenderer.updateTexture = function(texture)
{
	//TODO break this out into a texture manager...
	var gl = PIXI.gl;
	
	if(!texture._glTexture)
	{
		texture._glTexture = gl.createTexture();
	}

	if(texture.hasLoaded)
	{
		gl.bindTexture(gl.TEXTURE_2D, texture._glTexture);
	 	gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

		// reguler...

		if(!texture._powerOf2)
		{
			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);
		}
		else
		{
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
		}

		gl.bindTexture(gl.TEXTURE_2D, null);
	}
}

/**
 * Destroys a loaded webgl texture
 *
 * @method destroyTexture
 * @param texture {Texture} The texture to update
 * @private
 */
PIXI.WebGLRenderer.destroyTexture = function(texture)
{
	//TODO break this out into a texture manager...
	var gl = PIXI.gl;

	if(texture._glTexture)
	{
		texture._glTexture = gl.createTexture();
		gl.deleteTexture(gl.TEXTURE_2D, texture._glTexture);
	}
}

/**
 * resizes the webGL view to the specified width and height
 *
 * @method resize
 * @param width {Number} the new width of the webGL view
 * @param height {Number} the new height of the webGL view
 */
PIXI.WebGLRenderer.prototype.resize = function(width, height)
{
	this.width = width;
	this.height = height;

	this.view.width = width;
	this.view.height = height;

	this.gl.viewport(0, 0, this.width, this.height);	

	//var projectionMatrix = this.projectionMatrix;

	PIXI.projection.x =  this.width/2;
	PIXI.projection.y =  this.height/2;

//	projectionMatrix[0] = 2/this.width;
//	projectionMatrix[5] = -2/this.height;
//	projectionMatrix[12] = -1;
//	projectionMatrix[13] = 1;
}

/**
 * Handles a lost webgl context
 *
 * @method handleContextLost
 * @param event {Event}
 * @private
 */
PIXI.WebGLRenderer.prototype.handleContextLost = function(event)
{
	event.preventDefault();
	this.contextLost = true;
}

/**
 * Handles a restored webgl context
 *
 * @method handleContextRestored
 * @param event {Event}
 * @private
 */
PIXI.WebGLRenderer.prototype.handleContextRestored = function(event)
{
	this.gl = this.view.getContext("experimental-webgl",  {  	
		alpha: true
    });

	this.initShaders();	

	for(var key in PIXI.TextureCache) 
	{
        	var texture = PIXI.TextureCache[key].baseTexture;
        	texture._glTexture = null;
        	PIXI.WebGLRenderer.updateTexture(texture);
	};

	for (var i=0; i <  this.batchs.length; i++) 
	{
		this.batchs[i].restoreLostContext(this.gl)//
		this.batchs[i].dirty = true;
	};

	PIXI._restoreBatchs(this.gl);

	this.contextLost = false;
}

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

PIXI._batchs = [];

/**
 * @private
 */
PIXI._getBatch = function(gl)
{
	if(PIXI._batchs.length == 0)
	{
		return new PIXI.WebGLBatch(gl);
	}
	else
	{
		return PIXI._batchs.pop();
	}
}

/**
 * @private
 */
PIXI._returnBatch = function(batch)
{
	batch.clean();	
	PIXI._batchs.push(batch);
}

/**
 * @private
 */
PIXI._restoreBatchs = function(gl)
{
	for (var i=0; i < PIXI._batchs.length; i++) 
	{
	  PIXI._batchs[i].restoreLostContext(gl);
	};
}

/**
 * A WebGLBatch Enables a group of sprites to be drawn using the same settings.
 * if a group of sprites all have the same baseTexture and blendMode then they can be grouped into a batch.
 * All the sprites in a batch can then be drawn in one go by the GPU which is hugely efficient. ALL sprites
 * in the webGL renderer are added to a batch even if the batch only contains one sprite. Batching is handled
 * automatically by the webGL renderer. A good tip is: the smaller the number of batchs there are, the faster
 * the webGL renderer will run.
 *
 * @class WebGLBatch
 * @constructor
 * @param gl {WebGLContext} an instance of the webGL context
 */
PIXI.WebGLBatch = function(gl)
{
	this.gl = gl;
	
	this.size = 0;

	this.vertexBuffer =  gl.createBuffer();
	this.indexBuffer =  gl.createBuffer();
	this.uvBuffer =  gl.createBuffer();
	this.colorBuffer =  gl.createBuffer();
	this.blendMode = PIXI.blendModes.NORMAL;
	this.dynamicSize = 1;
}

// constructor
PIXI.WebGLBatch.prototype.constructor = PIXI.WebGLBatch;

/**
 * Cleans the batch so that is can be returned to an object pool and reused
 *
 * @method clean
 */
PIXI.WebGLBatch.prototype.clean = function()
{
	this.verticies = [];
	this.uvs = [];
	this.indices = [];
	this.colors = [];
	this.dynamicSize = 1;
	this.texture = null;
	this.last = null;
	this.size = 0;
	this.head;
	this.tail;
}

/**
 * Recreates the buffers in the event of a context loss
 *
 * @method restoreLostContext
 * @param gl {WebGLContext}
 */
PIXI.WebGLBatch.prototype.restoreLostContext = function(gl)
{
	this.gl = gl;
	this.vertexBuffer =  gl.createBuffer();
	this.indexBuffer =  gl.createBuffer();
	this.uvBuffer =  gl.createBuffer();
	this.colorBuffer =  gl.createBuffer();
}

/**
 * inits the batch's texture and blend mode based if the supplied sprite
 *
 * @method init
 * @param sprite {Sprite} the first sprite to be added to the batch. Only sprites with
 *		the same base texture and blend mode will be allowed to be added to this batch
 */	
PIXI.WebGLBatch.prototype.init = function(sprite)
{
	sprite.batch = this;
	this.dirty = true;
	this.blendMode = sprite.blendMode;
	this.texture = sprite.texture.baseTexture;
	this.head = sprite;
	this.tail = sprite;
	this.size = 1;

	this.growBatch();
}

/**
 * inserts a sprite before the specified sprite
 *
 * @method insertBefore
 * @param sprite {Sprite} the sprite to be added
 * @param nextSprite {nextSprite} the first sprite will be inserted before this sprite
 */	
PIXI.WebGLBatch.prototype.insertBefore = function(sprite, nextSprite)
{
	this.size++;

	sprite.batch = this;
	this.dirty = true;
	var tempPrev = nextSprite.__prev;
	nextSprite.__prev = sprite;
	sprite.__next = nextSprite;

	if(tempPrev)
	{
		sprite.__prev = tempPrev;
		tempPrev.__next = sprite;
	}
	else
	{
		this.head = sprite;
	}
}

/**
 * inserts a sprite after the specified sprite
 *
 * @method insertAfter
 * @param sprite {Sprite} the sprite to be added
 * @param  previousSprite {Sprite} the first sprite will be inserted after this sprite
 */	
PIXI.WebGLBatch.prototype.insertAfter = function(sprite, previousSprite)
{
	this.size++;

	sprite.batch = this;
	this.dirty = true;

	var tempNext = previousSprite.__next;
	previousSprite.__next = sprite;
	sprite.__prev = previousSprite;

	if(tempNext)
	{
		sprite.__next = tempNext;
		tempNext.__prev = sprite;
	}
	else
	{
		this.tail = sprite
	}
}

/**
 * removes a sprite from the batch
 *
 * @method remove
 * @param sprite {Sprite} the sprite to be removed
 */	
PIXI.WebGLBatch.prototype.remove = function(sprite)
{
	this.size--;

	if(this.size == 0)
	{
		sprite.batch = null;
		sprite.__prev = null;
		sprite.__next = null;
		return;
	}

	if(sprite.__prev)
	{
		sprite.__prev.__next = sprite.__next;
	}
	else
	{
		this.head = sprite.__next;
		this.head.__prev = null;
	}

	if(sprite.__next)
	{
		sprite.__next.__prev = sprite.__prev;
	}
	else
	{
		this.tail = sprite.__prev;
		this.tail.__next = null
	}

	sprite.batch = null;
	sprite.__next = null;
	sprite.__prev = null;
	this.dirty = true;
}

/**
 * Splits the batch into two with the specified sprite being the start of the new batch.
 *
 * @method split
 * @param sprite {Sprite} the sprite that indicates where the batch should be split
 * @return {WebGLBatch} the new batch
 */
PIXI.WebGLBatch.prototype.split = function(sprite)
{
	this.dirty = true;

	var batch = new PIXI.WebGLBatch(this.gl);
	batch.init(sprite);
	batch.texture = this.texture;
	batch.tail = this.tail;

	this.tail = sprite.__prev;
	this.tail.__next = null;

	sprite.__prev = null;
	// return a splite batch!

	// TODO this size is wrong!
	// need to recalculate :/ problem with a linked list!
	// unless it gets calculated in the "clean"?

	// need to loop through items as there is no way to know the length on a linked list :/
	var tempSize = 0;
	while(sprite)
	{
		tempSize++;
		sprite.batch = batch;
		sprite = sprite.__next;
	}

	batch.size = tempSize;
	this.size -= tempSize;

	return batch;
}

/**
 * Merges two batchs together
 *
 * @method merge
 * @param batch {WebGLBatch} the batch that will be merged 
 */
PIXI.WebGLBatch.prototype.merge = function(batch)
{
	this.dirty = true;

	this.tail.__next = batch.head;
	batch.head.__prev = this.tail;

	this.size += batch.size;

	this.tail = batch.tail;

	var sprite = batch.head;
	while(sprite)
	{
		sprite.batch = this;
		sprite = sprite.__next;
	}
}

/**
 * Grows the size of the batch. As the elements in the batch cannot have a dynamic size this
 * function is used to increase the size of the batch. It also creates a little extra room so
 * that the batch does not need to be resized every time a sprite is added
 *
 * @method growBatch
 */
PIXI.WebGLBatch.prototype.growBatch = function()
{
	var gl = this.gl;
	if( this.size == 1)
	{
		this.dynamicSize = 1;
	}
	else
	{
		this.dynamicSize = this.size * 1.5
	}
	// grow verts
	this.verticies = new Float32Array(this.dynamicSize * 8);

	gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
	gl.bufferData(gl.ARRAY_BUFFER,this.verticies , gl.DYNAMIC_DRAW);

	this.uvs  = new Float32Array( this.dynamicSize * 8 );
	gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, this.uvs , gl.DYNAMIC_DRAW);

	this.dirtyUVS = true;

	this.colors  = new Float32Array( this.dynamicSize * 4 );
	gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, this.colors , gl.DYNAMIC_DRAW);

	this.dirtyColors = true;

	this.indices = new Uint16Array(this.dynamicSize * 6); 
	var length = this.indices.length/6;

	for (var i=0; i < length; i++) 
	{
	    var index2 = i * 6;
	    var index3 = i * 4;
		this.indices[index2 + 0] = index3 + 0;
		this.indices[index2 + 1] = index3 + 1;
		this.indices[index2 + 2] = index3 + 2;
		this.indices[index2 + 3] = index3 + 0;
		this.indices[index2 + 4] = index3 + 2;
		this.indices[index2 + 5] = index3 + 3;
	};

	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
}

/**
 * Refresh's all the data in the batch and sync's it with the webGL buffers
 *
 * @method refresh
 */
PIXI.WebGLBatch.prototype.refresh = function()
{
	var gl = this.gl;

	if (this.dynamicSize < this.size)
	{
		this.growBatch();
	}

	var indexRun = 0;
	var worldTransform, width, height, aX, aY, w0, w1, h0, h1, index;
	var a, b, c, d, tx, ty;

	var displayObject = this.head;

	while(displayObject)
	{
		index = indexRun * 8;

		var texture = displayObject.texture;

		var frame = texture.frame;
		var tw = texture.baseTexture.width;
		var th = texture.baseTexture.height;

		this.uvs[index + 0] = frame.x / tw;
		this.uvs[index +1] = frame.y / th;

		this.uvs[index +2] = (frame.x + frame.width) / tw;
		this.uvs[index +3] = frame.y / th;

		this.uvs[index +4] = (frame.x + frame.width) / tw;
		this.uvs[index +5] = (frame.y + frame.height) / th; 

		this.uvs[index +6] = frame.x / tw;
		this.uvs[index +7] = (frame.y + frame.height) / th;

		displayObject.updateFrame = false;

		colorIndex = indexRun * 4;
		this.colors[colorIndex] = this.colors[colorIndex + 1] = this.colors[colorIndex + 2] = this.colors[colorIndex + 3] = displayObject.worldAlpha;

		displayObject = displayObject.__next;

		indexRun ++;
	}

	this.dirtyUVS = true;
	this.dirtyColors = true;
}

/**
 * Updates all the relevant geometry and uploads the data to the GPU
 *
 * @method update
 */
PIXI.WebGLBatch.prototype.update = function()
{
	var gl = this.gl;
	var worldTransform, width, height, aX, aY, w0, w1, h0, h1, index, index2, index3

	var a, b, c, d, tx, ty;

	var indexRun = 0;

	var displayObject = this.head;

	while(displayObject)
	{
		if(displayObject.vcount === PIXI.visibleCount)
		{
			width = displayObject.texture.frame.width;
			height = displayObject.texture.frame.height;

			// TODO trim??
			aX = displayObject.anchor.x;// - displayObject.texture.trim.x
			aY = displayObject.anchor.y; //- displayObject.texture.trim.y
			w0 = width * (1-aX);
			w1 = width * -aX;

			h0 = height * (1-aY);
			h1 = height * -aY;

			index = indexRun * 8;

			worldTransform = displayObject.worldTransform;

			a = worldTransform[0];
			b = worldTransform[3];
			c = worldTransform[1];
			d = worldTransform[4];
			tx = worldTransform[2];
			ty = worldTransform[5];

			this.verticies[index + 0 ] = a * w1 + c * h1 + tx; 
			this.verticies[index + 1 ] = d * h1 + b * w1 + ty;

			this.verticies[index + 2 ] = a * w0 + c * h1 + tx; 
			this.verticies[index + 3 ] = d * h1 + b * w0 + ty; 

			this.verticies[index + 4 ] = a * w0 + c * h0 + tx; 
			this.verticies[index + 5 ] = d * h0 + b * w0 + ty; 

			this.verticies[index + 6] =  a * w1 + c * h0 + tx; 
			this.verticies[index + 7] =  d * h0 + b * w1 + ty; 

			if(displayObject.updateFrame || displayObject.texture.updateFrame)
			{
				this.dirtyUVS = true;

				var texture = displayObject.texture;

				var frame = texture.frame;
				var tw = texture.baseTexture.width;
				var th = texture.baseTexture.height;

				this.uvs[index + 0] = frame.x / tw;
				this.uvs[index +1] = frame.y / th;

				this.uvs[index +2] = (frame.x + frame.width) / tw;
				this.uvs[index +3] = frame.y / th;

				this.uvs[index +4] = (frame.x + frame.width) / tw;
				this.uvs[index +5] = (frame.y + frame.height) / th; 

				this.uvs[index +6] = frame.x / tw;
				this.uvs[index +7] = (frame.y + frame.height) / th;

				displayObject.updateFrame = false;
			}

			// TODO this probably could do with some optimisation....
			if(displayObject.cacheAlpha != displayObject.worldAlpha)
			{
				displayObject.cacheAlpha = displayObject.worldAlpha;

				var colorIndex = indexRun * 4;
				this.colors[colorIndex] = this.colors[colorIndex + 1] = this.colors[colorIndex + 2] = this.colors[colorIndex + 3] = displayObject.worldAlpha;
				this.dirtyColors = true;
			}
		}
		else
		{
			index = indexRun * 8;

			this.verticies[index + 0 ] = 0;
			this.verticies[index + 1 ] = 0;

			this.verticies[index + 2 ] = 0;
			this.verticies[index + 3 ] = 0;

			this.verticies[index + 4 ] = 0;
			this.verticies[index + 5 ] = 0;

			this.verticies[index + 6] = 0;
			this.verticies[index + 7] = 0;
		}

		indexRun++;
		displayObject = displayObject.__next;
   }
}

/**
 * Draws the batch to the frame buffer
 *
 * @method render
 */
PIXI.WebGLBatch.prototype.render = function(start, end)
{
	start = start || 0;

	if(end == undefined)end = this.size;
	
	if(this.dirty)
	{
		this.refresh();
		this.dirty = false;
	}

	if (this.size == 0)return;

	this.update();
	var gl = this.gl;

	//TODO optimize this!

	var shaderProgram = PIXI.shaderProgram;
	gl.useProgram(shaderProgram);

	// update the verts..
	gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
	// ok..
	gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verticies)
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 2, gl.FLOAT, false, 0, 0);
	// update the uvs
   	gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);

    if(this.dirtyUVS)
    {
    	this.dirtyUVS = false;
    	gl.bufferSubData(gl.ARRAY_BUFFER,  0, this.uvs);
    }

    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, this.texture._glTexture);

	// update color!
	gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);

	if(this.dirtyColors)
    {
    	this.dirtyColors = false;
    	gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.colors);
	}

    gl.vertexAttribPointer(shaderProgram.colorAttribute, 1, gl.FLOAT, false, 0, 0);

	// dont need to upload!
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);

	var len = end - start;

    // DRAW THAT this!
    gl.drawElements(gl.TRIANGLES, len * 6, gl.UNSIGNED_SHORT, start * 2 * 6 );
}

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * A WebGLBatch Enables a group of sprites to be drawn using the same settings.
 * if a group of sprites all have the same baseTexture and blendMode then they can be
 * grouped into a batch. All the sprites in a batch can then be drawn in one go by the
 * GPU which is hugely efficient. ALL sprites in the webGL renderer are added to a batch
 * even if the batch only contains one sprite. Batching is handled automatically by the
 * webGL renderer. A good tip is: the smaller the number of batchs there are, the faster
 * the webGL renderer will run.
 *
 * @class WebGLBatch
 * @contructor
 * @param gl {WebGLContext} An instance of the webGL context
 */
PIXI.WebGLRenderGroup = function(gl)
{
	this.gl = gl;
	this.root;
	
	this.backgroundColor;
	this.batchs = [];
	this.toRemove = [];
}

// constructor
PIXI.WebGLRenderGroup.prototype.constructor = PIXI.WebGLRenderGroup;

/**
 * Add a display object to the webgl renderer
 *
 * @method setRenderable
 * @param displayObject {DisplayObject}
 * @private 
 */
PIXI.WebGLRenderGroup.prototype.setRenderable = function(displayObject)
{
	// has this changed??
	if(this.root)this.removeDisplayObjectAndChildren(this.root);
	
	displayObject.worldVisible = displayObject.visible;
	
	// soooooo //
	// to check if any batchs exist already??
	
	// TODO what if its already has an object? should remove it
	this.root = displayObject;
	this.addDisplayObjectAndChildren(displayObject);
}

/**
 * Renders the stage to its webgl view
 *
 * @method render
 * @param projection {Object}
 */
PIXI.WebGLRenderGroup.prototype.render = function(projection)
{
	PIXI.WebGLRenderer.updateTextures();
	
	var gl = this.gl;

	
	gl.uniform2f(PIXI.shaderProgram.projectionVector, projection.x, projection.y);
	gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
	
	// will render all the elements in the group
	var renderable;
	
	for (var i=0; i < this.batchs.length; i++) 
	{
		
		renderable = this.batchs[i];
		if(renderable instanceof PIXI.WebGLBatch)
		{
			this.batchs[i].render();
			continue;
		}
		
		// non sprite batch..
		var worldVisible = renderable.vcount === PIXI.visibleCount;

		if(renderable instanceof PIXI.TilingSprite)
		{
			if(worldVisible)this.renderTilingSprite(renderable, projection);
		}
		else if(renderable instanceof PIXI.Strip)
		{
			if(worldVisible)this.renderStrip(renderable, projection);
		}
		else if(renderable instanceof PIXI.Graphics)
		{
			if(worldVisible && renderable.renderable) PIXI.WebGLGraphics.renderGraphics(renderable, projection);//, projectionMatrix);
		}
		else if(renderable instanceof PIXI.FilterBlock)
		{
			/*
			 * for now only masks are supported..
			 */
			if(renderable.open)
			{
    			gl.enable(gl.STENCIL_TEST);
					
				gl.colorMask(false, false, false, false);
				gl.stencilFunc(gl.ALWAYS,1,0xff);
				gl.stencilOp(gl.KEEP,gl.KEEP,gl.REPLACE);
  
				PIXI.WebGLGraphics.renderGraphics(renderable.mask, projection);
  					
				gl.colorMask(true, true, true, false);
				gl.stencilFunc(gl.NOTEQUAL,0,0xff);
				gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP);
			}
			else
			{
				gl.disable(gl.STENCIL_TEST);
			}
		}
	}
	
}

/**
 * Renders the stage to its webgl view
 *
 * @method handleFilter
 * @param filter {FilterBlock}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.handleFilter = function(filter, projection)
{
	
}

/**
 * Renders a specific displayObject
 *
 * @method renderSpecific
 * @param displayObject {DisplayObject}
 * @param projection {Object}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.renderSpecific = function(displayObject, projection)
{
	PIXI.WebGLRenderer.updateTextures();
	
	var gl = this.gl;

	gl.uniform2f(PIXI.shaderProgram.projectionVector, projection.x, projection.y);

	// to do!
	// render part of the scene...
	
	var startIndex;
	var startBatchIndex;
	
	var endIndex;
	var endBatchIndex;
	
	/*
	 *  LOOK FOR THE NEXT SPRITE
	 *  This part looks for the closest next sprite that can go into a batch
	 *  it keeps looking until it finds a sprite or gets to the end of the display
	 *  scene graph
	 */
	var nextRenderable = displayObject.first;
	while(nextRenderable._iNext)
	{
		nextRenderable = nextRenderable._iNext;
		if(nextRenderable.renderable && nextRenderable.__renderGroup)break;
	}
	var startBatch = nextRenderable.batch;
	
	if(nextRenderable instanceof PIXI.Sprite)
	{
		startBatch = nextRenderable.batch;
		
		var head = startBatch.head;
		var next = head;
		
		// ok now we have the batch.. need to find the start index!
		if(head == nextRenderable)
		{
			startIndex = 0;
		}
		else
		{
			startIndex = 1;
			
			while(head.__next != nextRenderable)
			{
				startIndex++;
				head = head.__next;
			}
		}
	}
	else
	{
		startBatch = nextRenderable;
	}
	
	// Get the LAST renderable object
	var lastRenderable = displayObject;
	var endBatch;
	var lastItem = displayObject;
	while(lastItem.children.length > 0)
	{
		lastItem = lastItem.children[lastItem.children.length-1];
		if(lastItem.renderable)lastRenderable = lastItem;
	}
	
	if(lastRenderable instanceof PIXI.Sprite)
	{
		endBatch = lastRenderable.batch;
		
		var head = endBatch.head;
		
		if(head == lastRenderable)
		{
			endIndex = 0;
		}
		else
		{
			endIndex = 1;
			
			while(head.__next != lastRenderable)
			{
				endIndex++;
				head = head.__next;
			}
		}
	}
	else
	{
		endBatch = lastRenderable;
	}
	
	// TODO - need to fold this up a bit!
	
	if(startBatch == endBatch)
	{
		if(startBatch instanceof PIXI.WebGLBatch)
		{
			startBatch.render(startIndex, endIndex+1);
		}
		else
		{
			this.renderSpecial(startBatch, projection);
		}
		return;
	}
	
	// now we have first and last!
	startBatchIndex = this.batchs.indexOf(startBatch);
	endBatchIndex = this.batchs.indexOf(endBatch);
	
	// DO the first batch
	if(startBatch instanceof PIXI.WebGLBatch)
	{
		startBatch.render(startIndex);
	}
	else
	{
		this.renderSpecial(startBatch, projection);
	}
	
	// DO the middle batchs..
	for (var i=startBatchIndex+1; i < endBatchIndex; i++) 
	{
		renderable = this.batchs[i];
	
		if(renderable instanceof PIXI.WebGLBatch)
		{
			this.batchs[i].render();
		}
		else
		{
			this.renderSpecial(renderable, projection);
		}
	}
	
	// DO the last batch..
	if(endBatch instanceof PIXI.WebGLBatch)
	{
		endBatch.render(0, endIndex+1);
	}
	else
	{
		this.renderSpecial(endBatch, projection);
	}
}

/**
 * Renders a specific renderable
 *
 * @method renderSpecial
 * @param renderable {DisplayObject}
 * @param projection {Object}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.renderSpecial = function(renderable, projection)
{
	var worldVisible = renderable.vcount === PIXI.visibleCount

	if(renderable instanceof PIXI.TilingSprite)
	{
		if(worldVisible)this.renderTilingSprite(renderable, projection);
	}
	else if(renderable instanceof PIXI.Strip)
	{
		if(worldVisible)this.renderStrip(renderable, projection);
	}
	else if(renderable instanceof PIXI.CustomRenderable)
	{
		if(worldVisible) renderable.renderWebGL(this, projection);
	}
	else if(renderable instanceof PIXI.Graphics)
	{
		if(worldVisible && renderable.renderable) PIXI.WebGLGraphics.renderGraphics(renderable, projection);
	}
	else if(renderable instanceof PIXI.FilterBlock)
	{
		/*
		 * for now only masks are supported..
		 */

		var gl = PIXI.gl;

		if(renderable.open)
		{
			gl.enable(gl.STENCIL_TEST);
				
			gl.colorMask(false, false, false, false);
			gl.stencilFunc(gl.ALWAYS,1,0xff);
			gl.stencilOp(gl.KEEP,gl.KEEP,gl.REPLACE);
  
			PIXI.WebGLGraphics.renderGraphics(renderable.mask, projection);
			
			// we know this is a render texture so enable alpha too..
			gl.colorMask(true, true, true, true);
			gl.stencilFunc(gl.NOTEQUAL,0,0xff);
			gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP);
		}
		else
		{
			gl.disable(gl.STENCIL_TEST);
		}
	}
}

/**
 * Updates a webgl texture
 *
 * @method updateTexture
 * @param displayObject {DisplayObject}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject)
{
	
	// TODO definitely can optimse this function..
	
	this.removeObject(displayObject);
	
	/*
	 *  LOOK FOR THE PREVIOUS RENDERABLE
	 *  This part looks for the closest previous sprite that can go into a batch
	 *  It keeps going back until it finds a sprite or the stage
	 */
	var previousRenderable = displayObject.first;
	while(previousRenderable != this.root)
	{
		previousRenderable = previousRenderable._iPrev;
		if(previousRenderable.renderable && previousRenderable.__renderGroup)break;
	}
	
	/*
	 *  LOOK FOR THE NEXT SPRITE
	 *  This part looks for the closest next sprite that can go into a batch
	 *  it keeps looking until it finds a sprite or gets to the end of the display
	 *  scene graph
	 */
	var nextRenderable = displayObject.last;
	while(nextRenderable._iNext)
	{
		nextRenderable = nextRenderable._iNext;
		if(nextRenderable.renderable && nextRenderable.__renderGroup)break;
	}
	
	this.insertObject(displayObject, previousRenderable, nextRenderable);
}

/**
 * Adds filter blocks
 *
 * @method addFilterBlocks
 * @param start {FilterBlock}
 * @param end {FilterBlock}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.addFilterBlocks = function(start, end)
{
	start.__renderGroup = this;
	end.__renderGroup = this;
	/*
	 *  LOOK FOR THE PREVIOUS RENDERABLE
	 *  This part looks for the closest previous sprite that can go into a batch
	 *  It keeps going back until it finds a sprite or the stage
	 */
	var previousRenderable = start;
	while(previousRenderable != this.root)
	{
		previousRenderable = previousRenderable._iPrev;
		if(previousRenderable.renderable && previousRenderable.__renderGroup)break;
	}
	this.insertAfter(start, previousRenderable);
		
	/*
	 *  LOOK FOR THE NEXT SPRITE
	 *  This part looks for the closest next sprite that can go into a batch
	 *  it keeps looking until it finds a sprite or gets to the end of the display
	 *  scene graph
	 */
	var previousRenderable2 = end;
	while(previousRenderable2 != this.root)
	{
		previousRenderable2 = previousRenderable2._iPrev;
		if(previousRenderable2.renderable && previousRenderable2.__renderGroup)break;
	}
	this.insertAfter(end, previousRenderable2);
}

/**
 * Remove filter blocks
 *
 * @method removeFilterBlocks
 * @param start {FilterBlock}
 * @param end {FilterBlock}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.removeFilterBlocks = function(start, end)
{
	this.removeObject(start);
	this.removeObject(end);
}

/**
 * Adds a display object and children to the webgl context
 *
 * @method addDisplayObjectAndChildren
 * @param displayObject {DisplayObject}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.addDisplayObjectAndChildren = function(displayObject)
{
	if(displayObject.__renderGroup)displayObject.__renderGroup.removeDisplayObjectAndChildren(displayObject);
	
	/*
	 *  LOOK FOR THE PREVIOUS RENDERABLE
	 *  This part looks for the closest previous sprite that can go into a batch
	 *  It keeps going back until it finds a sprite or the stage
	 */
	
	var previousRenderable = displayObject.first;
	while(previousRenderable != this.root.first)
	{
		previousRenderable = previousRenderable._iPrev;
		if(previousRenderable.renderable && previousRenderable.__renderGroup)break;
	}
	
	/*
	 *  LOOK FOR THE NEXT SPRITE
	 *  This part looks for the closest next sprite that can go into a batch
	 *  it keeps looking until it finds a sprite or gets to the end of the display
	 *  scene graph
	 */
	var nextRenderable = displayObject.last;
	while(nextRenderable._iNext)
	{
		nextRenderable = nextRenderable._iNext;
		if(nextRenderable.renderable && nextRenderable.__renderGroup)break;
	}
	
	// one the display object hits this. we can break the loop	
	
	var tempObject = displayObject.first;
	var testObject = displayObject.last._iNext;
	do	
	{
		tempObject.__renderGroup = this;
		
		if(tempObject.renderable)
		{
		
			this.insertObject(tempObject, previousRenderable, nextRenderable);
			previousRenderable = tempObject;
		}
		
		tempObject = tempObject._iNext;
	}
	while(tempObject != testObject)
}

/**
 * Removes a display object and children to the webgl context
 *
 * @method removeDisplayObjectAndChildren
 * @param displayObject {DisplayObject}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.removeDisplayObjectAndChildren = function(displayObject)
{
	if(displayObject.__renderGroup != this)return;
	
//	var displayObject = displayObject.first;
	var lastObject = displayObject.last;
	do	
	{
		displayObject.__renderGroup = null;
		if(displayObject.renderable)this.removeObject(displayObject);
		displayObject = displayObject._iNext;
	}
	while(displayObject)
}

/**
 * Inserts a displayObject into the linked list
 *
 * @method insertObject
 * @param displayObject {DisplayObject}
 * @param previousObject {DisplayObject}
 * @param nextObject {DisplayObject}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.insertObject = function(displayObject, previousObject, nextObject)
{
	// while looping below THE OBJECT MAY NOT HAVE BEEN ADDED
	var previousSprite = previousObject;
	var nextSprite = nextObject;
	
	/*
	 * so now we have the next renderable and the previous renderable
	 * 
	 */
	if(displayObject instanceof PIXI.Sprite)
	{
		var previousBatch
		var nextBatch
		
		if(previousSprite instanceof PIXI.Sprite)
		{
			previousBatch = previousSprite.batch;
			if(previousBatch)
			{
				if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode)
				{
					previousBatch.insertAfter(displayObject, previousSprite);
					return;
				}
			}
		}
		else
		{
			// TODO reword!
			previousBatch = previousSprite;
		}
	
		if(nextSprite)
		{
			if(nextSprite instanceof PIXI.Sprite)
			{
				nextBatch = nextSprite.batch;
			
				//batch may not exist if item was added to the display list but not to the webGL
				if(nextBatch)
				{
					if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode)
					{
						nextBatch.insertBefore(displayObject, nextSprite);
						return;
					}
					else
					{
						if(nextBatch == previousBatch)
						{
							// THERE IS A SPLIT IN THIS BATCH! //
							var splitBatch = previousBatch.split(nextSprite);
							// COOL!
							// add it back into the array	
							/*
							 * OOPS!
							 * seems the new sprite is in the middle of a batch
							 * lets split it.. 
							 */
							var batch = PIXI.WebGLRenderer.getBatch();

							var index = this.batchs.indexOf( previousBatch );
							batch.init(displayObject);
							this.batchs.splice(index+1, 0, batch, splitBatch);
							
							return;
						}
					}
				}
			}
			else
			{
				// TODO re-word!
				
				nextBatch = nextSprite;
			}
		}
		
		/*
		 * looks like it does not belong to any batch!
		 * but is also not intersecting one..
		 * time to create anew one!
		 */
		
		var batch =  PIXI.WebGLRenderer.getBatch();
		batch.init(displayObject);

		if(previousBatch) // if this is invalid it means 
		{
			var index = this.batchs.indexOf( previousBatch );
			this.batchs.splice(index+1, 0, batch);
		}
		else
		{
			this.batchs.push(batch);
		}
		
		return;
	}
	else if(displayObject instanceof PIXI.TilingSprite)
	{
		
		// add to a batch!!
		this.initTilingSprite(displayObject);
	//	this.batchs.push(displayObject);
		
	}
	else if(displayObject instanceof PIXI.Strip)
	{
		// add to a batch!!
		this.initStrip(displayObject);
	//	this.batchs.push(displayObject);
	}
	else if(displayObject)// instanceof PIXI.Graphics)
	{
		//displayObject.initWebGL(this);
		
		// add to a batch!!
		//this.initStrip(displayObject);
		//this.batchs.push(displayObject);
	}
	
	this.insertAfter(displayObject, previousSprite);
			
	// insert and SPLIT!

}

/**
 * Inserts a displayObject into the linked list
 *
 * @method insertAfter
 * @param item {DisplayObject}
 * @param displayObject {DisplayObject} The object to insert
 * @private
 */
PIXI.WebGLRenderGroup.prototype.insertAfter = function(item, displayObject)
{
	if(displayObject instanceof PIXI.Sprite)
	{
		var previousBatch = displayObject.batch;
		
		if(previousBatch)
		{
			// so this object is in a batch!
			
			// is it not? need to split the batch
			if(previousBatch.tail == displayObject)
			{
				// is it tail? insert in to batchs	
				var index = this.batchs.indexOf( previousBatch );
				this.batchs.splice(index+1, 0, item);
			}
			else
			{
				// TODO MODIFY ADD / REMOVE CHILD TO ACCOUNT FOR FILTERS (also get prev and next) //
				
				// THERE IS A SPLIT IN THIS BATCH! //
				var splitBatch = previousBatch.split(displayObject.__next);
				
				// COOL!
				// add it back into the array	
				/*
				 * OOPS!
				 * seems the new sprite is in the middle of a batch
				 * lets split it.. 
				 */
				var index = this.batchs.indexOf( previousBatch );
				this.batchs.splice(index+1, 0, item, splitBatch);
			}
		}
		else
		{
			this.batchs.push(item);
		}
	}
	else
	{
		var index = this.batchs.indexOf( displayObject );
		this.batchs.splice(index+1, 0, item);
	}
}

/**
 * Removes a displayObject from the linked list
 *
 * @method removeObject
 * @param displayObject {DisplayObject} The object to remove
 * @private
 */
PIXI.WebGLRenderGroup.prototype.removeObject = function(displayObject)
{
	// loop through children..
	// display object //
	
	// add a child from the render group..
	// remove it and all its children!
	//displayObject.cacheVisible = false;//displayObject.visible;

	/*
	 * removing is a lot quicker..
	 * 
	 */
	var batchToRemove;
	
	if(displayObject instanceof PIXI.Sprite)
	{
		// should always have a batch!
		var batch = displayObject.batch;
		if(!batch)return; // this means the display list has been altered befre rendering
		
		batch.remove(displayObject);
		
		if(batch.size==0)
		{
			batchToRemove = batch;
		}
	}
	else
	{
		batchToRemove = displayObject;
	}
	
	/*
	 * Looks like there is somthing that needs removing!
	 */
	if(batchToRemove)	
	{
		var index = this.batchs.indexOf( batchToRemove );
		if(index == -1)return;// this means it was added then removed before rendered
		
		// ok so.. check to see if you adjacent batchs should be joined.
		// TODO may optimise?
		if(index == 0 || index == this.batchs.length-1)
		{
			// wha - eva! just get of the empty batch!
			this.batchs.splice(index, 1);
			if(batchToRemove instanceof PIXI.WebGLBatch)PIXI.WebGLRenderer.returnBatch(batchToRemove);
		
			return;
		}
		
		if(this.batchs[index-1] instanceof PIXI.WebGLBatch && this.batchs[index+1] instanceof PIXI.WebGLBatch)
		{
			if(this.batchs[index-1].texture == this.batchs[index+1].texture && this.batchs[index-1].blendMode == this.batchs[index+1].blendMode)
			{
				//console.log("MERGE")
				this.batchs[index-1].merge(this.batchs[index+1]);
				
				if(batchToRemove instanceof PIXI.WebGLBatch)PIXI.WebGLRenderer.returnBatch(batchToRemove);
				PIXI.WebGLRenderer.returnBatch(this.batchs[index+1]);
				this.batchs.splice(index, 2);
				return;
			}
		}
		
		this.batchs.splice(index, 1);
		if(batchToRemove instanceof PIXI.WebGLBatch)PIXI.WebGLRenderer.returnBatch(batchToRemove);
	}
}

/**
 * Initializes a tiling sprite
 *
 * @method initTilingSprite
 * @param sprite {TilingSprite} The tiling sprite to initialize
 * @private
 */
PIXI.WebGLRenderGroup.prototype.initTilingSprite = function(sprite)
{
	var gl = this.gl;

	// make the texture tilable..
			
	sprite.verticies = new Float32Array([0, 0,
										  sprite.width, 0,
										  sprite.width,  sprite.height,
										 0,  sprite.height]);
					
	sprite.uvs = new Float32Array([0, 0,
									1, 0,
									1, 1,
									0, 1]);
				
	sprite.colors = new Float32Array([1,1,1,1]);
	
	sprite.indices =  new Uint16Array([0, 1, 3,2])//, 2]);
	
	sprite._vertexBuffer = gl.createBuffer();
	sprite._indexBuffer = gl.createBuffer();
	sprite._uvBuffer = gl.createBuffer();
	sprite._colorBuffer = gl.createBuffer();
						
	gl.bindBuffer(gl.ARRAY_BUFFER, sprite._vertexBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, sprite.verticies, gl.STATIC_DRAW);

	gl.bindBuffer(gl.ARRAY_BUFFER, sprite._uvBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,  sprite.uvs, gl.DYNAMIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, sprite._colorBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, sprite.colors, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sprite._indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, sprite.indices, gl.STATIC_DRAW);
    
//    return ( (x > 0) && ((x & (x - 1)) == 0) );

	if(sprite.texture.baseTexture._glTexture)
	{
    	gl.bindTexture(gl.TEXTURE_2D, sprite.texture.baseTexture._glTexture);
    	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
		sprite.texture.baseTexture._powerOf2 = true;
	}
	else
	{
		sprite.texture.baseTexture._powerOf2 = true;
	}
}

/**
 * Renders a Strip
 *
 * @method renderStrip
 * @param strip {Strip} The strip to render
 * @param projection {Object}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.renderStrip = function(strip, projection)
{
	var gl = this.gl;
	var shaderProgram = PIXI.shaderProgram;
//	mat
	//var mat4Real = PIXI.mat3.toMat4(strip.worldTransform);
	//PIXI.mat4.transpose(mat4Real);
	//PIXI.mat4.multiply(projectionMatrix, mat4Real, mat4Real )

	
	gl.useProgram(PIXI.stripShaderProgram);

	var m = PIXI.mat3.clone(strip.worldTransform);
	
	PIXI.mat3.transpose(m);
	
	// set the matrix transform for the 
 	gl.uniformMatrix3fv(PIXI.stripShaderProgram.translationMatrix, false, m);
	gl.uniform2f(PIXI.stripShaderProgram.projectionVector, projection.x, projection.y);
	gl.uniform1f(PIXI.stripShaderProgram.alpha, strip.worldAlpha);

/*
	if(strip.blendMode == PIXI.blendModes.NORMAL)
	{
		gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
	}
	else
	{
		gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_COLOR);
	}
	*/
	
	
	if(!strip.dirty)
	{
		
		gl.bindBuffer(gl.ARRAY_BUFFER, strip._vertexBuffer);
		gl.bufferSubData(gl.ARRAY_BUFFER, 0, strip.verticies)
	    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 2, gl.FLOAT, false, 0, 0);
		
		// update the uvs
	   	gl.bindBuffer(gl.ARRAY_BUFFER, strip._uvBuffer);
	    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
			
	    gl.activeTexture(gl.TEXTURE0);
	    gl.bindTexture(gl.TEXTURE_2D, strip.texture.baseTexture._glTexture);
		
		gl.bindBuffer(gl.ARRAY_BUFFER, strip._colorBuffer);
	    gl.vertexAttribPointer(shaderProgram.colorAttribute, 1, gl.FLOAT, false, 0, 0);
		
		// dont need to upload!
	    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, strip._indexBuffer);
	}
	else
	{
		strip.dirty = false;
		gl.bindBuffer(gl.ARRAY_BUFFER, strip._vertexBuffer);
		gl.bufferData(gl.ARRAY_BUFFER, strip.verticies, gl.STATIC_DRAW)
	    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 2, gl.FLOAT, false, 0, 0);
		
		// update the uvs
	   	gl.bindBuffer(gl.ARRAY_BUFFER, strip._uvBuffer);
	   	gl.bufferData(gl.ARRAY_BUFFER, strip.uvs, gl.STATIC_DRAW)
	    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
			
	    gl.activeTexture(gl.TEXTURE0);
	    gl.bindTexture(gl.TEXTURE_2D, strip.texture.baseTexture._glTexture);
		
		gl.bindBuffer(gl.ARRAY_BUFFER, strip._colorBuffer);
		gl.bufferData(gl.ARRAY_BUFFER, strip.colors, gl.STATIC_DRAW)
	    gl.vertexAttribPointer(shaderProgram.colorAttribute, 1, gl.FLOAT, false, 0, 0);
		
		// dont need to upload!
	    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, strip._indexBuffer);
	    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, strip.indices, gl.STATIC_DRAW);
	    
	}
	//console.log(gl.TRIANGLE_STRIP);
	
	gl.drawElements(gl.TRIANGLE_STRIP, strip.indices.length, gl.UNSIGNED_SHORT, 0);
    
  	gl.useProgram(PIXI.shaderProgram);
}

/**
 * Renders a TilingSprite
 *
 * @method renderTilingSprite
 * @param sprite {TilingSprite} The tiling sprite to render
 * @param projectionMatrix {Object}
 * @private
 */
PIXI.WebGLRenderGroup.prototype.renderTilingSprite = function(sprite, projectionMatrix)
{
	var gl = this.gl;
	var shaderProgram = PIXI.shaderProgram;
	
	var tilePosition = sprite.tilePosition;
	var tileScale = sprite.tileScale;
	
	var offsetX =  tilePosition.x/sprite.texture.baseTexture.width;
	var offsetY =  tilePosition.y/sprite.texture.baseTexture.height;
	
	var scaleX =  (sprite.width / sprite.texture.baseTexture.width)  / tileScale.x;
	var scaleY =  (sprite.height / sprite.texture.baseTexture.height) / tileScale.y;

	sprite.uvs[0] = 0 - offsetX;
	sprite.uvs[1] = 0 - offsetY;
	
	sprite.uvs[2] = (1 * scaleX)  -offsetX;
	sprite.uvs[3] = 0 - offsetY;
	
	sprite.uvs[4] = (1 *scaleX) - offsetX;
	sprite.uvs[5] = (1 *scaleY) - offsetY;
	
	sprite.uvs[6] = 0 - offsetX;
	sprite.uvs[7] = (1 *scaleY) - offsetY;
	
	gl.bindBuffer(gl.ARRAY_BUFFER, sprite._uvBuffer);
	gl.bufferSubData(gl.ARRAY_BUFFER, 0, sprite.uvs)
	
	this.renderStrip(sprite, projectionMatrix);
}

/**
 * Initializes a strip to be rendered
 *
 * @method initStrip
 * @param strip {Strip} The strip to initialize
 * @private
 */
PIXI.WebGLRenderGroup.prototype.initStrip = function(strip)
{
	// build the strip!
	var gl = this.gl;
	var shaderProgram = this.shaderProgram;
	
	strip._vertexBuffer = gl.createBuffer();
	strip._indexBuffer = gl.createBuffer();
	strip._uvBuffer = gl.createBuffer();
	strip._colorBuffer = gl.createBuffer();
	
	gl.bindBuffer(gl.ARRAY_BUFFER, strip._vertexBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, strip.verticies, gl.DYNAMIC_DRAW);

	gl.bindBuffer(gl.ARRAY_BUFFER, strip._uvBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,  strip.uvs, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, strip._colorBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, strip.colors, gl.STATIC_DRAW);

	
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, strip._indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, strip.indices, gl.STATIC_DRAW);
}

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */


/**
 * the CanvasRenderer draws the stage and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL.
 * Dont forget to add the view to your DOM or you will not see anything :)
 *
 * @class CanvasRenderer
 * @constructor
 * @param width=0 {Number} the width of the canvas view
 * @param height=0 {Number} the height of the canvas view
 * @param view {Canvas} the canvas to use as a view, optional
 * @param transparent=false {Boolean} the transparency of the render view, default false
 */
PIXI.CanvasRenderer = function(width, height, view, transparent)
{
	this.transparent = transparent;

	/**
	 * The width of the canvas view
	 *
	 * @property width
	 * @type Number
	 * @default 800
	 */
	this.width = width || 800;

	/**
	 * The height of the canvas view
	 *
	 * @property height
	 * @type Number
	 * @default 600
	 */
	this.height = height || 600;

	/**
	 * The canvas element that the everything is drawn to
	 *
	 * @property view
	 * @type Canvas
	 */
	this.view = view || document.createElement( 'canvas' );

	/**
	 * The canvas context that the everything is drawn to
	 * @property context
	 * @type Canvas 2d Context
	 */
	this.context = this.view.getContext("2d");

	this.refresh = true;
	// hack to enable some hardware acceleration!
	//this.view.style["transform"] = "translatez(0)";
	
    this.view.width = this.width;
	this.view.height = this.height;  
	this.count = 0;
}

// constructor
PIXI.CanvasRenderer.prototype.constructor = PIXI.CanvasRenderer;

/**
 * Renders the stage to its canvas view
 *
 * @method render
 * @param stage {Stage} the Stage element to be rendered
 */
PIXI.CanvasRenderer.prototype.render = function(stage)
{
	
	//stage.__childrenAdded = [];
	//stage.__childrenRemoved = [];
	
	// update textures if need be
	PIXI.texturesToUpdate = [];
	PIXI.texturesToDestroy = [];
	
	PIXI.visibleCount++;
	stage.updateTransform();
	
	// update the background color
	if(this.view.style.backgroundColor!=stage.backgroundColorString && !this.transparent)this.view.style.backgroundColor = stage.backgroundColorString;

	this.context.setTransform(1,0,0,1,0,0); 
	this.context.clearRect(0, 0, this.width, this.height)
    this.renderDisplayObject(stage);
    //as
   
    // run interaction!
	if(stage.interactive)
	{
		//need to add some events!
		if(!stage._interactiveEventsAdded)
		{
			stage._interactiveEventsAdded = true;
			stage.interactionManager.setTarget(this);
		}
	}
	
	// remove frame updates..
	if(PIXI.Texture.frameUpdates.length > 0)
	{
		PIXI.Texture.frameUpdates = [];
	}
	
	
}

/**
 * resizes the canvas view to the specified width and height
 *
 * @method resize
 * @param width {Number} the new width of the canvas view
 * @param height {Number} the new height of the canvas view
 */
PIXI.CanvasRenderer.prototype.resize = function(width, height)
{
	this.width = width;
	this.height = height;
	
	this.view.width = width;
	this.view.height = height;
}

/**
 * Renders a display object
 *
 * @method renderDisplayObject
 * @param displayObject {DisplayObject} The displayObject to render
 * @private
 */
PIXI.CanvasRenderer.prototype.renderDisplayObject = function(displayObject)
{
	// no loger recurrsive!
	var transform;
	var context = this.context;
	
	context.globalCompositeOperation = 'source-over';
	
	// one the display object hits this. we can break the loop	
	var testObject = displayObject.last._iNext;
	displayObject = displayObject.first;
	
	do	
	{
		transform = displayObject.worldTransform;
		
		if(!displayObject.visible)
		{
			displayObject = displayObject.last._iNext;
			continue;
		}
		
		if(!displayObject.renderable)
		{
			displayObject = displayObject._iNext;
			continue;
		}
		
		if(displayObject instanceof PIXI.Sprite)
		{
				
			var frame = displayObject.texture.frame;
			
			if(frame)
			{
				context.globalAlpha = displayObject.worldAlpha;
				
				context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]);
					
				context.drawImage(displayObject.texture.baseTexture.source, 
								   frame.x,
								   frame.y,
								   frame.width,
								   frame.height,
								   (displayObject.anchor.x) * -frame.width, 
								   (displayObject.anchor.y) * -frame.height,
								   frame.width,
								   frame.height);
			}					   
	   	}
	   	else if(displayObject instanceof PIXI.Strip)
		{
			context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5])
			this.renderStrip(displayObject);
		}
		else if(displayObject instanceof PIXI.TilingSprite)
		{
			context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5])
			this.renderTilingSprite(displayObject);
		}
		else if(displayObject instanceof PIXI.CustomRenderable)
		{
			displayObject.renderCanvas(this);
		}
		else if(displayObject instanceof PIXI.Graphics)
		{
			context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5])
			PIXI.CanvasGraphics.renderGraphics(displayObject, context);
		}
		else if(displayObject instanceof PIXI.FilterBlock)
		{
			if(displayObject.open)
			{
				context.save();
				
				var cacheAlpha = displayObject.mask.alpha;
				var maskTransform = displayObject.mask.worldTransform;
				
				context.setTransform(maskTransform[0], maskTransform[3], maskTransform[1], maskTransform[4], maskTransform[2], maskTransform[5])
				
				displayObject.mask.worldAlpha = 0.5;
				
				context.worldAlpha = 0;
				
				PIXI.CanvasGraphics.renderGraphicsMask(displayObject.mask, context);
				context.clip();
				
				displayObject.mask.worldAlpha = cacheAlpha;
			}
			else
			{
				context.restore();
			}
		}
	//	count++
		displayObject = displayObject._iNext;
		
		
	}
	while(displayObject != testObject)

	
}

/**
 * Renders a flat strip
 *
 * @method renderStripFlat
 * @param strip {Strip} The Strip to render
 * @private
 */
PIXI.CanvasRenderer.prototype.renderStripFlat = function(strip)
{
	var context = this.context;
	var verticies = strip.verticies;
	var uvs = strip.uvs;
	
	var length = verticies.length/2;
	this.count++;
	
	context.beginPath();
	for (var i=1; i < length-2; i++) 
	{
		
		// draw some triangles!
		var index = i*2;
		
		 var x0 = verticies[index],   x1 = verticies[index+2], x2 = verticies[index+4];
 		 var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5];
 		 
		context.moveTo(x0, y0);
		context.lineTo(x1, y1);
		context.lineTo(x2, y2);
		
	};	
	
	context.fillStyle = "#FF0000";
	context.fill();
	context.closePath();
}

/**
 * Renders a tiling sprite
 *
 * @method renderTilingSprite
 * @param sprite {TilingSprite} The tilingsprite to render
 * @private
 */
PIXI.CanvasRenderer.prototype.renderTilingSprite = function(sprite)
{
	var context = this.context;
	
	context.globalAlpha = sprite.worldAlpha;
	
 	if(!sprite.__tilePattern) sprite.__tilePattern = context.createPattern(sprite.texture.baseTexture.source, "repeat");
 	
	context.beginPath();
	
	var tilePosition = sprite.tilePosition;
	var tileScale = sprite.tileScale;
	
    // offset
    context.scale(tileScale.x,tileScale.y);
    context.translate(tilePosition.x, tilePosition.y);
 	
	context.fillStyle = sprite.__tilePattern;
	context.fillRect(-tilePosition.x,-tilePosition.y,sprite.width / tileScale.x, sprite.height / tileScale.y);
	
	context.scale(1/tileScale.x, 1/tileScale.y);
    context.translate(-tilePosition.x, -tilePosition.y);
    
    context.closePath();
}

/**
 * Renders a strip
 *
 * @method renderStrip
 * @param strip {Strip} The Strip to render
 * @private
 */
PIXI.CanvasRenderer.prototype.renderStrip = function(strip)
{
	var context = this.context;

	// draw triangles!!
	var verticies = strip.verticies;
	var uvs = strip.uvs;
	
	var length = verticies.length/2;
	this.count++;
	for (var i=1; i < length-2; i++) 
	{
		
		// draw some triangles!
		var index = i*2;
		
		 var x0 = verticies[index],   x1 = verticies[index+2], x2 = verticies[index+4];
 		 var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5];
 		 
  		 var u0 = uvs[index] * strip.texture.width,   u1 = uvs[index+2] * strip.texture.width, u2 = uvs[index+4]* strip.texture.width;
   		 var v0 = uvs[index+1]* strip.texture.height, v1 = uvs[index+3] * strip.texture.height, v2 = uvs[index+5]* strip.texture.height;


		context.save();
		context.beginPath();
		context.moveTo(x0, y0);
		context.lineTo(x1, y1);
		context.lineTo(x2, y2);
		context.closePath();
		
		context.clip();
		
		
        // Compute matrix transform
        var delta = u0*v1 + v0*u2 + u1*v2 - v1*u2 - v0*u1 - u0*v2;
        var delta_a = x0*v1 + v0*x2 + x1*v2 - v1*x2 - v0*x1 - x0*v2;
        var delta_b = u0*x1 + x0*u2 + u1*x2 - x1*u2 - x0*u1 - u0*x2;
        var delta_c = u0*v1*x2 + v0*x1*u2 + x0*u1*v2 - x0*v1*u2 - v0*u1*x2 - u0*x1*v2;
        var delta_d = y0*v1 + v0*y2 + y1*v2 - v1*y2 - v0*y1 - y0*v2;
        var delta_e = u0*y1 + y0*u2 + u1*y2 - y1*u2 - y0*u1 - u0*y2;
        var delta_f = u0*v1*y2 + v0*y1*u2 + y0*u1*v2 - y0*v1*u2 - v0*u1*y2 - u0*y1*v2;
		
		
		
		    
        context.transform(delta_a/delta, delta_d/delta,
                      delta_b/delta, delta_e/delta,
                      delta_c/delta, delta_f/delta);
                 
		context.drawImage(strip.texture.baseTexture.source, 0, 0);
	  	context.restore();
	};
	
}

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */


/**
 * A set of functions used by the canvas renderer to draw the primitive graphics data
 *
 * @class CanvasGraphics
 */
PIXI.CanvasGraphics = function()
{
	
}


/*
 * Renders the graphics object
 *
 * @static
 * @private
 * @method renderGraphics
 * @param graphics {Graphics}
 * @param context {Context2D}
 */
PIXI.CanvasGraphics.renderGraphics = function(graphics, context)
{
	var worldAlpha = graphics.worldAlpha;
	
	for (var i=0; i < graphics.graphicsData.length; i++) 
	{
		var data = graphics.graphicsData[i];
		var points = data.points;
		
		context.strokeStyle = color = '#' + ('00000' + ( data.lineColor | 0).toString(16)).substr(-6);

		context.lineWidth = data.lineWidth;
		
		if(data.type == PIXI.Graphics.POLY)
		{
			context.beginPath();
			
			context.moveTo(points[0], points[1]);
			
			for (var j=1; j < points.length/2; j++)
			{
				context.lineTo(points[j * 2], points[j * 2 + 1]);
			} 
	      	
	      	// if the first and last point are the same close the path - much neater :)
	      	if(points[0] == points[points.length-2] && points[1] == points[points.length-1])
	      	{
	      		context.closePath();
	      	}
			
			if(data.fill)
			{
				context.globalAlpha = data.fillAlpha * worldAlpha;
				context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6);
      			context.fill();
			}
			if(data.lineWidth)
			{
				context.globalAlpha = data.lineAlpha * worldAlpha;
      			context.stroke();
			}
		}
		else if(data.type == PIXI.Graphics.RECT)
		{
				
			// TODO - need to be Undefined!
			if(data.fillColor != undefined)
			{
				context.globalAlpha = data.fillAlpha * worldAlpha;
				context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6);
				context.fillRect(points[0], points[1], points[2], points[3]);
				
			}
			if(data.lineWidth)
			{
				context.globalAlpha = data.lineAlpha * worldAlpha;
				context.strokeRect(points[0], points[1], points[2], points[3]);
			}
			
		}
		else if(data.type == PIXI.Graphics.CIRC)
		{
			// TODO - need to be Undefined!
      		context.beginPath();
			context.arc(points[0], points[1], points[2],0,2*Math.PI);
			context.closePath();
			
			if(data.fill)
			{
				context.globalAlpha = data.fillAlpha * worldAlpha;
				context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6);
      			context.fill();
			}
			if(data.lineWidth)
			{
				context.globalAlpha = data.lineAlpha * worldAlpha;
      			context.stroke();
			}
		}
		else if(data.type == PIXI.Graphics.ELIP)
		{
			
			// elipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
			
			var elipseData =  data.points;
			
			var w = elipseData[2] * 2;
			var h = elipseData[3] * 2;
	
			var x = elipseData[0] - w/2;
			var y = elipseData[1] - h/2;
			
      		context.beginPath();
			
			var kappa = .5522848,
			ox = (w / 2) * kappa, // control point offset horizontal
			oy = (h / 2) * kappa, // control point offset vertical
			xe = x + w,           // x-end
			ye = y + h,           // y-end
			xm = x + w / 2,       // x-middle
			ym = y + h / 2;       // y-middle
			
			context.moveTo(x, ym);
			context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
			context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
			context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
			context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  
			context.closePath();
			
			if(data.fill)
			{
				context.globalAlpha = data.fillAlpha * worldAlpha;
				context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6);
      			context.fill();
			}
			if(data.lineWidth)
			{
				context.globalAlpha = data.lineAlpha * worldAlpha;
      			context.stroke();
			}
		}
      	
	};
}

/*
 * Renders a graphics mask
 *
 * @static
 * @private
 * @method renderGraphicsMask
 * @param graphics {Graphics}
 * @param context {Context2D}
 */
PIXI.CanvasGraphics.renderGraphicsMask = function(graphics, context)
{
	var worldAlpha = graphics.worldAlpha;
	
	var len = graphics.graphicsData.length;
	if(len > 1)
	{
		len = 1;
		console.log("Pixi.js warning: masks in canvas can only mask using the first path in the graphics object")
	}
	
	for (var i=0; i < 1; i++) 
	{
		var data = graphics.graphicsData[i];
		var points = data.points;
		
		if(data.type == PIXI.Graphics.POLY)
		{
			context.beginPath();
			context.moveTo(points[0], points[1]);
			
			for (var j=1; j < points.length/2; j++)
			{
				context.lineTo(points[j * 2], points[j * 2 + 1]);
			} 
	      	
	      	// if the first and last point are the same close the path - much neater :)
	      	if(points[0] == points[points.length-2] && points[1] == points[points.length-1])
	      	{
	      		context.closePath();
	      	}
			
		}
		else if(data.type == PIXI.Graphics.RECT)
		{
			context.beginPath();
			context.rect(points[0], points[1], points[2], points[3]);
			context.closePath();
		}
		else if(data.type == PIXI.Graphics.CIRC)
		{
			// TODO - need to be Undefined!
      		context.beginPath();
			context.arc(points[0], points[1], points[2],0,2*Math.PI);
			context.closePath();
		}
		else if(data.type == PIXI.Graphics.ELIP)
		{
			
			// elipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
			var elipseData =  data.points;
			
			var w = elipseData[2] * 2;
			var h = elipseData[3] * 2;
	
			var x = elipseData[0] - w/2;
			var y = elipseData[1] - h/2;
			
      		context.beginPath();
			
			var kappa = .5522848,
			ox = (w / 2) * kappa, // control point offset horizontal
			oy = (h / 2) * kappa, // control point offset vertical
			xe = x + w,           // x-end
			ye = y + h,           // y-end
			xm = x + w / 2,       // x-middle
			ym = y + h / 2;       // y-middle
			
			context.moveTo(x, ym);
			context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
			context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
			context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
			context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
			context.closePath();
		}
      	
	   
	};
}

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */


/**
 * The Graphics class contains a set of methods that you can use to create primitive shapes and lines. 
 * It is important to know that with the webGL renderer only simple polys can be filled at this stage
 * Complex polys will not be filled. Heres an example of a complex poly: http://www.goodboydigital.com/wp-content/uploads/2013/06/complexPolygon.png
 *
 * @class Graphics 
 * @extends DisplayObjectContainer
 * @constructor
 */
PIXI.Graphics = function()
{
	PIXI.DisplayObjectContainer.call( this );
	
	this.renderable = true;

    /**
     * The alpha of the fill of this graphics object
     *
     * @property fillAlpha
     * @type Number
     */
	this.fillAlpha = 1;

    /**
     * The width of any lines drawn
     *
     * @property lineWidth
     * @type Number
     */
	this.lineWidth = 0;

    /**
     * The color of any lines drawn
     *
     * @property lineColor
     * @type String
     */
	this.lineColor = "black";

    /**
     * Graphics data
     *
     * @property graphicsData
     * @type Array
     * @private
     */
	this.graphicsData = [];

    /**
     * Current path
     *
     * @property currentPath
     * @type Object
     * @private
     */
	this.currentPath = {points:[]};
}

// constructor
PIXI.Graphics.prototype = Object.create( PIXI.DisplayObjectContainer.prototype );
PIXI.Graphics.prototype.constructor = PIXI.Graphics;

/**
 * Specifies a line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method.
 *
 * @method lineStyle
 * @param lineWidth {Number} width of the line to draw, will update the object's stored style
 * @param color {Number} color of the line to draw, will update the object's stored style
 * @param alpha {Number} alpha of the line to draw, will update the object's stored style
 */
PIXI.Graphics.prototype.lineStyle = function(lineWidth, color, alpha)
{
	if(this.currentPath.points.length == 0)this.graphicsData.pop();
	
	this.lineWidth = lineWidth || 0;
	this.lineColor = color || 0;
	this.lineAlpha = (alpha == undefined) ? 1 : alpha;
	
	this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, 
						fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[], type:PIXI.Graphics.POLY};
	
	this.graphicsData.push(this.currentPath);
}

/**
 * Moves the current drawing position to (x, y).
 *
 * @method moveTo
 * @param x {Number} the X coord to move to
 * @param y {Number} the Y coord to move to
 */
PIXI.Graphics.prototype.moveTo = function(x, y)
{
	if(this.currentPath.points.length == 0)this.graphicsData.pop();
	
	this.currentPath = this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, 
						fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[], type:PIXI.Graphics.POLY};
	
	this.currentPath.points.push(x, y);
	
	this.graphicsData.push(this.currentPath);
}

/**
 * Draws a line using the current line style from the current drawing position to (x, y);
 * the current drawing position is then set to (x, y).
 *
 * @method lineTo
 * @param x {Number} the X coord to draw to
 * @param y {Number} the Y coord to draw to
 */
PIXI.Graphics.prototype.lineTo = function(x, y)
{
	this.currentPath.points.push(x, y);
	this.dirty = true;
}

/**
 * Specifies a simple one-color fill that subsequent calls to other Graphics methods
 * (such as lineTo() or drawCircle()) use when drawing.
 *
 * @method beginFill
 * @param color {uint} the color of the fill
 * @param alpha {Number} the alpha
 */
PIXI.Graphics.prototype.beginFill = function(color, alpha)
{
	this.filling = true;
	this.fillColor = color || 0;
	this.fillAlpha = (alpha == undefined) ? 1 : alpha;
}

/**
 * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method.
 *
 * @method endFill
 */
PIXI.Graphics.prototype.endFill = function()
{
	this.filling = false;
	this.fillColor = null;
	this.fillAlpha = 1;
}

/**
 * @method drawRect
 *
 * @param x {Number} The X coord of the top-left of the rectangle
 * @param y {Number} The Y coord of the top-left of the rectangle
 * @param width {Number} The width of the rectangle
 * @param height {Number} The height of the rectangle
 */
PIXI.Graphics.prototype.drawRect = function( x, y, width, height )
{
	if(this.currentPath.points.length == 0)this.graphicsData.pop();
	
	this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, 
						fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, 
						points:[x, y, width, height], type:PIXI.Graphics.RECT};
						
	this.graphicsData.push(this.currentPath);
	this.dirty = true;
}

/**
 * Draws a circle.
 *
 * @method drawCircle
 * @param x {Number} The X coord of the center of the circle
 * @param y {Number} The Y coord of the center of the circle
 * @param radius {Number} The radius of the circle
 */
PIXI.Graphics.prototype.drawCircle = function( x, y, radius)
{
	if(this.currentPath.points.length == 0)this.graphicsData.pop();
	
	this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, 
						fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, 
						points:[x, y, radius, radius], type:PIXI.Graphics.CIRC};
						
	this.graphicsData.push(this.currentPath);
	this.dirty = true;
}

/**
 * Draws an elipse.
 *
 * @method drawElipse
 * @param x {Number}
 * @param y {Number}
 * @param width {Number}
 * @param height {Number}
 */
PIXI.Graphics.prototype.drawElipse = function( x, y, width, height)
{
	if(this.currentPath.points.length == 0)this.graphicsData.pop();
	
	this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, 
						fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, 
						points:[x, y, width, height], type:PIXI.Graphics.ELIP};
						
	this.graphicsData.push(this.currentPath);
	this.dirty = true;
}

/**
 * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings.
 *
 * @method clear
 */
PIXI.Graphics.prototype.clear = function()
{
	this.lineWidth = 0;
	this.filling = false;
	
	this.dirty = true;
	this.clearDirty = true;
	this.graphicsData = [];
}

// SOME TYPES:
PIXI.Graphics.POLY = 0;
PIXI.Graphics.RECT = 1;
PIXI.Graphics.CIRC = 2;
PIXI.Graphics.ELIP = 3;

/**
 * @author Mat Groves http://matgroves.com/
 */

PIXI.Strip = function(texture, width, height)
{
	PIXI.DisplayObjectContainer.call( this );
	this.texture = texture;
	this.blendMode = PIXI.blendModes.NORMAL;
	
	try
	{
		this.uvs = new Float32Array([0, 1,
				1, 1,
				1, 0, 0,1]);
	
		this.verticies = new Float32Array([0, 0,
						  0,0,
						  0,0, 0,
						  0, 0]);
						  
		this.colors = new Float32Array([1, 1, 1, 1]);
		
		this.indices = new Uint16Array([0, 1, 2, 3]);
	}
	catch(error)
	{
		this.uvs = [0, 1,
				1, 1,
				1, 0, 0,1];
	
		this.verticies = [0, 0,
						  0,0,
						  0,0, 0,
						  0, 0];
						  
		this.colors = [1, 1, 1, 1];
		
		this.indices = [0, 1, 2, 3];
	}
	
	
	/*
	this.uvs = new Float32Array()
	this.verticies = new Float32Array()
	this.colors = new Float32Array()
	this.indices = new Uint16Array()
*/
	this.width = width;
	this.height = height;
	
	// load the texture!
	if(texture.baseTexture.hasLoaded)
	{
		this.width   = this.texture.frame.width;
		this.height  = this.texture.frame.height;
		this.updateFrame = true;
	}
	else
	{
		this.onTextureUpdateBind = this.onTextureUpdate.bind(this);
		this.texture.addEventListener( 'update', this.onTextureUpdateBind );
	}
	
	this.renderable = true;
}

// constructor
PIXI.Strip.prototype = Object.create( PIXI.DisplayObjectContainer.prototype );
PIXI.Strip.prototype.constructor = PIXI.Strip;

PIXI.Strip.prototype.setTexture = function(texture)
{
	//TODO SET THE TEXTURES
	//TODO VISIBILITY
	
	// stop current texture 
	this.texture = texture;
	this.width   = texture.frame.width;
	this.height  = texture.frame.height;
	this.updateFrame = true;
}

PIXI.Strip.prototype.onTextureUpdate = function(event)
{
	this.updateFrame = true;
}
// some helper functions..


/**
 * @author Mat Groves http://matgroves.com/
 */


PIXI.Rope = function(texture, points)
{
	PIXI.Strip.call( this, texture );
	this.points = points;
	
	try
	{
		this.verticies = new Float32Array( points.length * 4);
		this.uvs = new Float32Array( points.length * 4);
		this.colors = new Float32Array(  points.length * 2);
		this.indices = new Uint16Array( points.length * 2);
	}
	catch(error)
	{
		this.verticies = verticies
		
		this.uvs = uvs
		this.colors = colors
		this.indices = indices
	}
	
	this.refresh();
}


// constructor
PIXI.Rope.prototype = Object.create( PIXI.Strip.prototype );
PIXI.Rope.prototype.constructor = PIXI.Rope;

PIXI.Rope.prototype.refresh = function()
{
	var points = this.points;
	if(points.length < 1)return;
	
	var uvs = this.uvs
	var indices = this.indices;
	var colors = this.colors;
	
	var lastPoint = points[0];
	var nextPoint;
	var perp = {x:0, y:0};
	var point = points[0];
	
	this.count-=0.2;
	
	
	uvs[0] = 0
	uvs[1] = 1
	uvs[2] = 0
	uvs[3] = 1
	
	colors[0] = 1;
	colors[1] = 1;
	
	indices[0] = 0;
	indices[1] = 1;
	
	var total = points.length;
		
	for (var i =  1; i < total; i++) 
	{
		
		var point = points[i];
		var index = i * 4;
		// time to do some smart drawing!
		var amount = i/(total-1)
		
		if(i%2)
		{
			uvs[index] = amount;
			uvs[index+1] = 0;
			
			uvs[index+2] = amount
			uvs[index+3] = 1
		
		}
		else
		{
			uvs[index] = amount
			uvs[index+1] = 0
			
			uvs[index+2] = amount
			uvs[index+3] = 1
		}
		
		index = i * 2;
		colors[index] = 1;
		colors[index+1] = 1;
		
		index = i * 2;
		indices[index] = index;
		indices[index + 1] = index + 1;
		
		lastPoint = point;
	}
}

PIXI.Rope.prototype.updateTransform = function()
{
	
	var points = this.points;
	if(points.length < 1)return;
	
	var verticies = this.verticies 
	
	var lastPoint = points[0];
	var nextPoint;
	var perp = {x:0, y:0};
	var point = points[0];
	
	this.count-=0.2;
	
	verticies[0] = point.x + perp.x 
	verticies[1] = point.y + perp.y //+ 200
	verticies[2] = point.x - perp.x 
	verticies[3] = point.y - perp.y//+200
	// time to do some smart drawing!
	
	var total = points.length;
		
	for (var i =  1; i < total; i++) 
	{
		
		var point = points[i];
		var index = i * 4;
		
		if(i < points.length-1)
		{
			nextPoint = points[i+1];
		}
		else
		{
			nextPoint = point
		}
		
		perp.y = -(nextPoint.x - lastPoint.x);
		perp.x = nextPoint.y - lastPoint.y;
		
		var ratio = (1 - (i / (total-1))) * 10;
				if(ratio > 1)ratio = 1;
				
		var perpLength = Math.sqrt(perp.x * perp.x + perp.y * perp.y);
		var num = this.texture.height/2//(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio;
		perp.x /= perpLength;
		perp.y /= perpLength;
	
		perp.x *= num;
		perp.y *= num;
		
		verticies[index] = point.x + perp.x 
		verticies[index+1] = point.y + perp.y
		verticies[index+2] = point.x - perp.x 
		verticies[index+3] = point.y - perp.y

		lastPoint = point;
	}
	
	PIXI.DisplayObjectContainer.prototype.updateTransform.call( this );
}

PIXI.Rope.prototype.setTexture = function(texture)
{
	// stop current texture 
	this.texture = texture;
	this.updateFrame = true;
}





/**
 * @author Mat Groves http://matgroves.com/
 */

/**
 * A tiling sprite is a fast way of rendering a tiling image
 *
 * @class TilingSprite
 * @extends DisplayObjectContainer
 * @constructor
 * @param texture {Texture} the texture of the tiling sprite
 * @param width {Number}  the width of the tiling sprite
 * @param height {Number} the height of the tiling sprite
 */
PIXI.TilingSprite = function(texture, width, height)
{
	PIXI.DisplayObjectContainer.call( this );

	/**
	 * The texture that the sprite is using
	 *
	 * @property texture
	 * @type Texture
	 */
	this.texture = texture;

	/**
	 * The width of the tiling sprite
	 *
	 * @property width
	 * @type Number
	 */
	this.width = width;

	/**
	 * The height of the tiling sprite
	 *
	 * @property height
	 * @type Number
	 */
	this.height = height;

	/**
	 * The scaling of the image that is being tiled
	 *
	 * @property tileScale
	 * @type Point
	 */	
	this.tileScale = new PIXI.Point(1,1);

	/**
	 * The offset position of the image that is being tiled
	 *
	 * @property tilePosition
	 * @type Point
	 */	
	this.tilePosition = new PIXI.Point(0,0);

	this.renderable = true;
	
	this.blendMode = PIXI.blendModes.NORMAL
}

// constructor
PIXI.TilingSprite.prototype = Object.create( PIXI.DisplayObjectContainer.prototype );
PIXI.TilingSprite.prototype.constructor = PIXI.TilingSprite;

/**
 * Sets the texture of the tiling sprite
 *
 * @method setTexture
 * @param texture {Texture} The PIXI texture that is displayed by the sprite
 */
PIXI.TilingSprite.prototype.setTexture = function(texture)
{
	//TODO SET THE TEXTURES
	//TODO VISIBILITY
	
	// stop current texture 
	this.texture = texture;
	this.updateFrame = true;
}

/**
 * When the texture is updated, this event will fire to update the frame
 *
 * @method onTextureUpdate
 * @param event
 * @private
 */
PIXI.TilingSprite.prototype.onTextureUpdate = function(event)
{
	this.updateFrame = true;
}


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 * based on pixi impact spine implementation made by Eemeli Kelokorpi (@ekelokorpi) https://github.com/ekelokorpi
 *
 * Awesome JS run time provided by EsotericSoftware
 * https://github.com/EsotericSoftware/spine-runtimes
 *
 */

/**
 * A class that enables the you to import and run your spine animations in pixi.
 * Spine animation data needs to be loaded using the PIXI.AssetLoader or PIXI.SpineLoader before it can be used by this class
 * See example 12 (http://www.goodboydigital.com/pixijs/examples/12/) to see a working example and check out the source
 *
 * @class Spine
 * @extends DisplayObjectContainer
 * @constructor
 * @param url {String} The url of the spine anim file to be used
 */
PIXI.Spine = function (url) {
	PIXI.DisplayObjectContainer.call(this);

	this.spineData = PIXI.AnimCache[url];

	if (!this.spineData) {
		throw new Error("Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: " + url);
	}

	this.skeleton = new spine.Skeleton(this.spineData);
	this.skeleton.updateWorldTransform();

	this.stateData = new spine.AnimationStateData(this.spineData);
	this.state = new spine.AnimationState(this.stateData);

	this.slotContainers = [];

	for (var i = 0, n = this.skeleton.drawOrder.length; i < n; i++) {
		var slot = this.skeleton.drawOrder[i];
		var attachment = slot.attachment;
		var slotContainer = new PIXI.DisplayObjectContainer();
		this.slotContainers.push(slotContainer);
		this.addChild(slotContainer);
		if (!(attachment instanceof spine.RegionAttachment)) {
			continue;
		}
		var spriteName = attachment.rendererObject.name;
		var sprite = this.createSprite(slot, attachment.rendererObject);
		slot.currentSprite = sprite;
		slot.currentSpriteName = spriteName;
		slotContainer.addChild(sprite);
	}
};

PIXI.Spine.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
PIXI.Spine.prototype.constructor = PIXI.Spine;

/*
 * Updates the object transform for rendering
 *
 * @method updateTransform
 * @private
 */
PIXI.Spine.prototype.updateTransform = function () {
	this.lastTime = this.lastTime || Date.now();
	var timeDelta = (Date.now() - this.lastTime) * 0.001;
	this.lastTime = Date.now();
	this.state.update(timeDelta);
	this.state.apply(this.skeleton);
	this.skeleton.updateWorldTransform();

	var drawOrder = this.skeleton.drawOrder;
	for (var i = 0, n = drawOrder.length; i < n; i++) {
		var slot = drawOrder[i];
		var attachment = slot.attachment;
		var slotContainer = this.slotContainers[i];
		if (!(attachment instanceof spine.RegionAttachment)) {
			slotContainer.visible = false;
			continue;
		}

		if (attachment.rendererObject) {
			if (!slot.currentSpriteName || slot.currentSpriteName != attachment.name) {
				var spriteName = attachment.rendererObject.name;
				if (slot.currentSprite !== undefined) {
					slot.currentSprite.visible = false;
				}
				slot.sprites = slot.sprites || {};
				if (slot.sprites[spriteName] !== undefined) {
					slot.sprites[spriteName].visible = true;
				} else {
					var sprite = this.createSprite(slot, attachment.rendererObject);
					slotContainer.addChild(sprite);
				}
				slot.currentSprite = slot.sprites[spriteName];
				slot.currentSpriteName = spriteName;
			}
		}
		slotContainer.visible = true;

		var bone = slot.bone;

		slotContainer.position.x = bone.worldX + attachment.x * bone.m00 + attachment.y * bone.m01;
		slotContainer.position.y = bone.worldY + attachment.x * bone.m10 + attachment.y * bone.m11;
		slotContainer.scale.x = bone.worldScaleX;
		slotContainer.scale.y = bone.worldScaleY;

		slotContainer.rotation = -(slot.bone.worldRotation * Math.PI / 180);
	}

	PIXI.DisplayObjectContainer.prototype.updateTransform.call(this);
};


PIXI.Spine.prototype.createSprite = function (slot, descriptor) {
	var name = PIXI.TextureCache[descriptor.name] ? descriptor.name : descriptor.name + ".png";
	var sprite = new PIXI.Sprite(PIXI.Texture.fromFrame(name));
	sprite.scale = descriptor.scale;
	sprite.rotation = descriptor.rotation;
	sprite.anchor.x = sprite.anchor.y = 0.5;

	slot.sprites = slot.sprites || {};
	slot.sprites[descriptor.name] = sprite;
	return sprite;
};

/*
 * Awesome JS run time provided by EsotericSoftware
 * 
 * https://github.com/EsotericSoftware/spine-runtimes
 * 
 */

var spine = {};

spine.BoneData = function (name, parent) {
	this.name = name;
	this.parent = parent;
};
spine.BoneData.prototype = {
	length: 0,
	x: 0, y: 0,
	rotation: 0,
	scaleX: 1, scaleY: 1
};

spine.SlotData = function (name, boneData) {
	this.name = name;
	this.boneData = boneData;
};
spine.SlotData.prototype = {
	r: 1, g: 1, b: 1, a: 1,
	attachmentName: null
};

spine.Bone = function (boneData, parent) {
	this.data = boneData;
	this.parent = parent;
	this.setToSetupPose();
};
spine.Bone.yDown = false;
spine.Bone.prototype = {
	x: 0, y: 0,
	rotation: 0,
	scaleX: 1, scaleY: 1,
	m00: 0, m01: 0, worldX: 0, // a b x
	m10: 0, m11: 0, worldY: 0, // c d y
	worldRotation: 0,
	worldScaleX: 1, worldScaleY: 1,
	updateWorldTransform: function (flipX, flipY) {
		var parent = this.parent;
		if (parent != null) {
			this.worldX = this.x * parent.m00 + this.y * parent.m01 + parent.worldX;
			this.worldY = this.x * parent.m10 + this.y * parent.m11 + parent.worldY;
			this.worldScaleX = parent.worldScaleX * this.scaleX;
			this.worldScaleY = parent.worldScaleY * this.scaleY;
			this.worldRotation = parent.worldRotation + this.rotation;
		} else {
			this.worldX = this.x;
			this.worldY = this.y;
			this.worldScaleX = this.scaleX;
			this.worldScaleY = this.scaleY;
			this.worldRotation = this.rotation;
		}
		var radians = this.worldRotation * Math.PI / 180;
		var cos = Math.cos(radians);
		var sin = Math.sin(radians);
		this.m00 = cos * this.worldScaleX;
		this.m10 = sin * this.worldScaleX;
		this.m01 = -sin * this.worldScaleY;
		this.m11 = cos * this.worldScaleY;
		if (flipX) {
			this.m00 = -this.m00;
			this.m01 = -this.m01;
		}
		if (flipY) {
			this.m10 = -this.m10;
			this.m11 = -this.m11;
		}
		if (spine.Bone.yDown) {
			this.m10 = -this.m10;
			this.m11 = -this.m11;
		}
	},
	setToSetupPose: function () {
		var data = this.data;
		this.x = data.x;
		this.y = data.y;
		this.rotation = data.rotation;
		this.scaleX = data.scaleX;
		this.scaleY = data.scaleY;
	}
};

spine.Slot = function (slotData, skeleton, bone) {
	this.data = slotData;
	this.skeleton = skeleton;
	this.bone = bone;
	this.setToSetupPose();
};
spine.Slot.prototype = {
	r: 1, g: 1, b: 1, a: 1,
	_attachmentTime: 0,
	attachment: null,
	setAttachment: function (attachment) {
		this.attachment = attachment;
		this._attachmentTime = this.skeleton.time;
	},
	setAttachmentTime: function (time) {
		this._attachmentTime = this.skeleton.time - time;
	},
	getAttachmentTime: function () {
		return this.skeleton.time - this._attachmentTime;
	},
	setToSetupPose: function () {
		var data = this.data;
		this.r = data.r;
		this.g = data.g;
		this.b = data.b;
		this.a = data.a;

		var slotDatas = this.skeleton.data.slots;
		for (var i = 0, n = slotDatas.length; i < n; i++) {
			if (slotDatas[i] == data) {
				this.setAttachment(!data.attachmentName ? null : this.skeleton.getAttachmentBySlotIndex(i, data.attachmentName));
				break;
			}
		}
	}
};

spine.Skin = function (name) {
	this.name = name;
	this.attachments = {};
};
spine.Skin.prototype = {
	addAttachment: function (slotIndex, name, attachment) {
		this.attachments[slotIndex + ":" + name] = attachment;
	},
	getAttachment: function (slotIndex, name) {
		return this.attachments[slotIndex + ":" + name];
	},
	_attachAll: function (skeleton, oldSkin) {
		for (var key in oldSkin.attachments) {
			var colon = key.indexOf(":");
			var slotIndex = parseInt(key.substring(0, colon));
			var name = key.substring(colon + 1);
			var slot = skeleton.slots[slotIndex];
			if (slot.attachment && slot.attachment.name == name) {
				var attachment = this.getAttachment(slotIndex, name);
				if (attachment) slot.setAttachment(attachment);
			}
		}
	}
};

spine.Animation = function (name, timelines, duration) {
	this.name = name;
	this.timelines = timelines;
	this.duration = duration;
};
spine.Animation.prototype = {
	apply: function (skeleton, time, loop) {
		if (loop && this.duration != 0) time %= this.duration;
		var timelines = this.timelines;
		for (var i = 0, n = timelines.length; i < n; i++)
			timelines[i].apply(skeleton, time, 1);
	},
	mix: function (skeleton, time, loop, alpha) {
		if (loop && this.duration != 0) time %= this.duration;
		var timelines = this.timelines;
		for (var i = 0, n = timelines.length; i < n; i++)
			timelines[i].apply(skeleton, time, alpha);
	}
};

spine.binarySearch = function (values, target, step) {
	var low = 0;
	var high = Math.floor(values.length / step) - 2;
	if (high == 0) return step;
	var current = high >>> 1;
	while (true) {
		if (values[(current + 1) * step] <= target)
			low = current + 1;
		else
			high = current;
		if (low == high) return (low + 1) * step;
		current = (low + high) >>> 1;
	}
};
spine.linearSearch = function (values, target, step) {
	for (var i = 0, last = values.length - step; i <= last; i += step)
		if (values[i] > target) return i;
	return -1;
};

spine.Curves = function (frameCount) {
	this.curves = []; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ...
	this.curves.length = (frameCount - 1) * 6;
};
spine.Curves.prototype = {
	setLinear: function (frameIndex) {
		this.curves[frameIndex * 6] = 0/*LINEAR*/;
	},
	setStepped: function (frameIndex) {
		this.curves[frameIndex * 6] = -1/*STEPPED*/;
	},
	/** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
	 * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
	 * the difference between the keyframe's values. */
	setCurve: function (frameIndex, cx1, cy1, cx2, cy2) {
		var subdiv_step = 1 / 10/*BEZIER_SEGMENTS*/;
		var subdiv_step2 = subdiv_step * subdiv_step;
		var subdiv_step3 = subdiv_step2 * subdiv_step;
		var pre1 = 3 * subdiv_step;
		var pre2 = 3 * subdiv_step2;
		var pre4 = 6 * subdiv_step2;
		var pre5 = 6 * subdiv_step3;
		var tmp1x = -cx1 * 2 + cx2;
		var tmp1y = -cy1 * 2 + cy2;
		var tmp2x = (cx1 - cx2) * 3 + 1;
		var tmp2y = (cy1 - cy2) * 3 + 1;
		var i = frameIndex * 6;
		var curves = this.curves;
		curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
		curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
		curves[i + 2] = tmp1x * pre4 + tmp2x * pre5;
		curves[i + 3] = tmp1y * pre4 + tmp2y * pre5;
		curves[i + 4] = tmp2x * pre5;
		curves[i + 5] = tmp2y * pre5;
	},
	getCurvePercent: function (frameIndex, percent) {
		percent = percent < 0 ? 0 : (percent > 1 ? 1 : percent);
		var curveIndex = frameIndex * 6;
		var curves = this.curves;
		var dfx = curves[curveIndex];
		if (!dfx/*LINEAR*/) return percent;
		if (dfx == -1/*STEPPED*/) return 0;
		var dfy = curves[curveIndex + 1];
		var ddfx = curves[curveIndex + 2];
		var ddfy = curves[curveIndex + 3];
		var dddfx = curves[curveIndex + 4];
		var dddfy = curves[curveIndex + 5];
		var x = dfx, y = dfy;
		var i = 10/*BEZIER_SEGMENTS*/ - 2;
		while (true) {
			if (x >= percent) {
				var lastX = x - dfx;
				var lastY = y - dfy;
				return lastY + (y - lastY) * (percent - lastX) / (x - lastX);
			}
			if (i == 0) break;
			i--;
			dfx += ddfx;
			dfy += ddfy;
			ddfx += dddfx;
			ddfy += dddfy;
			x += dfx;
			y += dfy;
		}
		return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
	}
};

spine.RotateTimeline = function (frameCount) {
	this.curves = new spine.Curves(frameCount);
	this.frames = []; // time, angle, ...
	this.frames.length = frameCount * 2;
};
spine.RotateTimeline.prototype = {
	boneIndex: 0,
	getFrameCount: function () {
		return this.frames.length / 2;
	},
	setFrame: function (frameIndex, time, angle) {
		frameIndex *= 2;
		this.frames[frameIndex] = time;
		this.frames[frameIndex + 1] = angle;
	},
	apply: function (skeleton, time, alpha) {
		var frames = this.frames;
		if (time < frames[0]) return; // Time is before first frame.

		var bone = skeleton.bones[this.boneIndex];

		if (time >= frames[frames.length - 2]) { // Time is after last frame.
			var amount = bone.data.rotation + frames[frames.length - 1] - bone.rotation;
			while (amount > 180)
				amount -= 360;
			while (amount < -180)
				amount += 360;
			bone.rotation += amount * alpha;
			return;
		}

		// Interpolate between the last frame and the current frame.
		var frameIndex = spine.binarySearch(frames, time, 2);
		var lastFrameValue = frames[frameIndex - 1];
		var frameTime = frames[frameIndex];
		var percent = 1 - (time - frameTime) / (frames[frameIndex - 2/*LAST_FRAME_TIME*/] - frameTime);
		percent = this.curves.getCurvePercent(frameIndex / 2 - 1, percent);

		var amount = frames[frameIndex + 1/*FRAME_VALUE*/] - lastFrameValue;
		while (amount > 180)
			amount -= 360;
		while (amount < -180)
			amount += 360;
		amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation;
		while (amount > 180)
			amount -= 360;
		while (amount < -180)
			amount += 360;
		bone.rotation += amount * alpha;
	}
};

spine.TranslateTimeline = function (frameCount) {
	this.curves = new spine.Curves(frameCount);
	this.frames = []; // time, x, y, ...
	this.frames.length = frameCount * 3;
};
spine.TranslateTimeline.prototype = {
	boneIndex: 0,
	getFrameCount: function () {
		return this.frames.length / 3;
	},
	setFrame: function (frameIndex, time, x, y) {
		frameIndex *= 3;
		this.frames[frameIndex] = time;
		this.frames[frameIndex + 1] = x;
		this.frames[frameIndex + 2] = y;
	},
	apply: function (skeleton, time, alpha) {
		var frames = this.frames;
		if (time < frames[0]) return; // Time is before first frame.

		var bone = skeleton.bones[this.boneIndex];

		if (time >= frames[frames.length - 3]) { // Time is after last frame.
			bone.x += (bone.data.x + frames[frames.length - 2] - bone.x) * alpha;
			bone.y += (bone.data.y + frames[frames.length - 1] - bone.y) * alpha;
			return;
		}

		// Interpolate between the last frame and the current frame.
		var frameIndex = spine.binarySearch(frames, time, 3);
		var lastFrameX = frames[frameIndex - 2];
		var lastFrameY = frames[frameIndex - 1];
		var frameTime = frames[frameIndex];
		var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime);
		percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent);

		bone.x += (bone.data.x + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.x) * alpha;
		bone.y += (bone.data.y + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.y) * alpha;
	}
};

spine.ScaleTimeline = function (frameCount) {
	this.curves = new spine.Curves(frameCount);
	this.frames = []; // time, x, y, ...
	this.frames.length = frameCount * 3;
};
spine.ScaleTimeline.prototype = {
	boneIndex: 0,
	getFrameCount: function () {
		return this.frames.length / 3;
	},
	setFrame: function (frameIndex, time, x, y) {
		frameIndex *= 3;
		this.frames[frameIndex] = time;
		this.frames[frameIndex + 1] = x;
		this.frames[frameIndex + 2] = y;
	},
	apply: function (skeleton, time, alpha) {
		var frames = this.frames;
		if (time < frames[0]) return; // Time is before first frame.

		var bone = skeleton.bones[this.boneIndex];

		if (time >= frames[frames.length - 3]) { // Time is after last frame.
			bone.scaleX += (bone.data.scaleX - 1 + frames[frames.length - 2] - bone.scaleX) * alpha;
			bone.scaleY += (bone.data.scaleY - 1 + frames[frames.length - 1] - bone.scaleY) * alpha;
			return;
		}

		// Interpolate between the last frame and the current frame.
		var frameIndex = spine.binarySearch(frames, time, 3);
		var lastFrameX = frames[frameIndex - 2];
		var lastFrameY = frames[frameIndex - 1];
		var frameTime = frames[frameIndex];
		var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime);
		percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent);

		bone.scaleX += (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.scaleX) * alpha;
		bone.scaleY += (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.scaleY) * alpha;
	}
};

spine.ColorTimeline = function (frameCount) {
	this.curves = new spine.Curves(frameCount);
	this.frames = []; // time, r, g, b, a, ...
	this.frames.length = frameCount * 5;
};
spine.ColorTimeline.prototype = {
	slotIndex: 0,
	getFrameCount: function () {
		return this.frames.length / 2;
	},
	setFrame: function (frameIndex, time, x, y) {
		frameIndex *= 5;
		this.frames[frameIndex] = time;
		this.frames[frameIndex + 1] = r;
		this.frames[frameIndex + 2] = g;
		this.frames[frameIndex + 3] = b;
		this.frames[frameIndex + 4] = a;
	},
	apply: function (skeleton, time, alpha) {
		var frames = this.frames;
		if (time < frames[0]) return; // Time is before first frame.

		var slot = skeleton.slots[this.slotIndex];

		if (time >= frames[frames.length - 5]) { // Time is after last frame.
			var i = frames.length - 1;
			slot.r = frames[i - 3];
			slot.g = frames[i - 2];
			slot.b = frames[i - 1];
			slot.a = frames[i];
			return;
		}

		// Interpolate between the last frame and the current frame.
		var frameIndex = spine.binarySearch(frames, time, 5);
		var lastFrameR = frames[frameIndex - 4];
		var lastFrameG = frames[frameIndex - 3];
		var lastFrameB = frames[frameIndex - 2];
		var lastFrameA = frames[frameIndex - 1];
		var frameTime = frames[frameIndex];
		var percent = 1 - (time - frameTime) / (frames[frameIndex - 5/*LAST_FRAME_TIME*/] - frameTime);
		percent = this.curves.getCurvePercent(frameIndex / 5 - 1, percent);

		var r = lastFrameR + (frames[frameIndex + 1/*FRAME_R*/] - lastFrameR) * percent;
		var g = lastFrameG + (frames[frameIndex + 2/*FRAME_G*/] - lastFrameG) * percent;
		var b = lastFrameB + (frames[frameIndex + 3/*FRAME_B*/] - lastFrameB) * percent;
		var a = lastFrameA + (frames[frameIndex + 4/*FRAME_A*/] - lastFrameA) * percent;
		if (alpha < 1) {
			slot.r += (r - slot.r) * alpha;
			slot.g += (g - slot.g) * alpha;
			slot.b += (b - slot.b) * alpha;
			slot.a += (a - slot.a) * alpha;
		} else {
			slot.r = r;
			slot.g = g;
			slot.b = b;
			slot.a = a;
		}
	}
};

spine.AttachmentTimeline = function (frameCount) {
	this.curves = new spine.Curves(frameCount);
	this.frames = []; // time, ...
	this.frames.length = frameCount;
	this.attachmentNames = []; // time, ...
	this.attachmentNames.length = frameCount;
};
spine.AttachmentTimeline.prototype = {
	slotIndex: 0,
	getFrameCount: function () {
            return this.frames.length;
	},
	setFrame: function (frameIndex, time, attachmentName) {
		this.frames[frameIndex] = time;
		this.attachmentNames[frameIndex] = attachmentName;
	},
	apply: function (skeleton, time, alpha) {
		var frames = this.frames;
		if (time < frames[0]) return; // Time is before first frame.

		var frameIndex;
		if (time >= frames[frames.length - 1]) // Time is after last frame.
			frameIndex = frames.length - 1;
		else
			frameIndex = spine.binarySearch(frames, time, 1) - 1;

		var attachmentName = this.attachmentNames[frameIndex];
		skeleton.slots[this.slotIndex].setAttachment(!attachmentName ? null : skeleton.getAttachmentBySlotIndex(this.slotIndex, attachmentName));
	}
};

spine.SkeletonData = function () {
	this.bones = [];
	this.slots = [];
	this.skins = [];
	this.animations = [];
};
spine.SkeletonData.prototype = {
	defaultSkin: null,
	/** @return May be null. */
	findBone: function (boneName) {
		var bones = this.bones;
		for (var i = 0, n = bones.length; i < n; i++)
			if (bones[i].name == boneName) return bones[i];
		return null;
	},
	/** @return -1 if the bone was not found. */
	findBoneIndex: function (boneName) {
		var bones = this.bones;
		for (var i = 0, n = bones.length; i < n; i++)
			if (bones[i].name == boneName) return i;
		return -1;
	},
	/** @return May be null. */
	findSlot: function (slotName) {
		var slots = this.slots;
		for (var i = 0, n = slots.length; i < n; i++) {
			if (slots[i].name == slotName) return slot[i];
		}
		return null;
	},
	/** @return -1 if the bone was not found. */
	findSlotIndex: function (slotName) {
		var slots = this.slots;
		for (var i = 0, n = slots.length; i < n; i++)
			if (slots[i].name == slotName) return i;
		return -1;
	},
	/** @return May be null. */
	findSkin: function (skinName) {
		var skins = this.skins;
		for (var i = 0, n = skins.length; i < n; i++)
			if (skins[i].name == skinName) return skins[i];
		return null;
	},
	/** @return May be null. */
	findAnimation: function (animationName) {
		var animations = this.animations;
		for (var i = 0, n = animations.length; i < n; i++)
			if (animations[i].name == animationName) return animations[i];
		return null;
	}
};

spine.Skeleton = function (skeletonData) {
	this.data = skeletonData;

	this.bones = [];
	for (var i = 0, n = skeletonData.bones.length; i < n; i++) {
		var boneData = skeletonData.bones[i];
		var parent = !boneData.parent ? null : this.bones[skeletonData.bones.indexOf(boneData.parent)];
		this.bones.push(new spine.Bone(boneData, parent));
	}

	this.slots = [];
	this.drawOrder = [];
	for (var i = 0, n = skeletonData.slots.length; i < n; i++) {
		var slotData = skeletonData.slots[i];
		var bone = this.bones[skeletonData.bones.indexOf(slotData.boneData)];
		var slot = new spine.Slot(slotData, this, bone);
		this.slots.push(slot);
		this.drawOrder.push(slot);
	}
};
spine.Skeleton.prototype = {
	x: 0, y: 0,
	skin: null,
	r: 1, g: 1, b: 1, a: 1,
	time: 0,
	flipX: false, flipY: false,
	/** Updates the world transform for each bone. */
	updateWorldTransform: function () {
		var flipX = this.flipX;
		var flipY = this.flipY;
		var bones = this.bones;
		for (var i = 0, n = bones.length; i < n; i++)
			bones[i].updateWorldTransform(flipX, flipY);
	},
	/** Sets the bones and slots to their setup pose values. */
	setToSetupPose: function () {
		this.setBonesToSetupPose();
		this.setSlotsToSetupPose();
	},
	setBonesToSetupPose: function () {
		var bones = this.bones;
		for (var i = 0, n = bones.length; i < n; i++)
			bones[i].setToSetupPose();
	},
	setSlotsToSetupPose: function () {
		var slots = this.slots;
		for (var i = 0, n = slots.length; i < n; i++)
			slots[i].setToSetupPose(i);
	},
	/** @return May return null. */
	getRootBone: function () {
		return this.bones.length == 0 ? null : this.bones[0];
	},
	/** @return May be null. */
	findBone: function (boneName) {
		var bones = this.bones;
		for (var i = 0, n = bones.length; i < n; i++)
			if (bones[i].data.name == boneName) return bones[i];
		return null;
	},
	/** @return -1 if the bone was not found. */
	findBoneIndex: function (boneName) {
		var bones = this.bones;
		for (var i = 0, n = bones.length; i < n; i++)
			if (bones[i].data.name == boneName) return i;
		return -1;
	},
	/** @return May be null. */
	findSlot: function (slotName) {
		var slots = this.slots;
		for (var i = 0, n = slots.length; i < n; i++)
			if (slots[i].data.name == slotName) return slots[i];
		return null;
	},
	/** @return -1 if the bone was not found. */
	findSlotIndex: function (slotName) {
		var slots = this.slots;
		for (var i = 0, n = slots.length; i < n; i++)
			if (slots[i].data.name == slotName) return i;
		return -1;
	},
	setSkinByName: function (skinName) {
		var skin = this.data.findSkin(skinName);
		if (!skin) throw "Skin not found: " + skinName;
		this.setSkin(skin);
	},
	/** Sets the skin used to look up attachments not found in the {@link SkeletonData#getDefaultSkin() default skin}. Attachments
	 * from the new skin are attached if the corresponding attachment from the old skin was attached.
	 * @param newSkin May be null. */
	setSkin: function (newSkin) {
		if (this.skin && newSkin) newSkin._attachAll(this, this.skin);
		this.skin = newSkin;
	},
	/** @return May be null. */
	getAttachmentBySlotName: function (slotName, attachmentName) {
		return this.getAttachmentBySlotIndex(this.data.findSlotIndex(slotName), attachmentName);
	},
	/** @return May be null. */
	getAttachmentBySlotIndex: function (slotIndex, attachmentName) {
		if (this.skin) {
			var attachment = this.skin.getAttachment(slotIndex, attachmentName);
			if (attachment) return attachment;
		}
		if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName);
		return null;
	},
	/** @param attachmentName May be null. */
	setAttachment: function (slotName, attachmentName) {
		var slots = this.slots;
		for (var i = 0, n = slots.size; i < n; i++) {
			var slot = slots[i];
			if (slot.data.name == slotName) {
				var attachment = null;
				if (attachmentName) {
					attachment = this.getAttachment(i, attachmentName);
					if (attachment == null) throw "Attachment not found: " + attachmentName + ", for slot: " + slotName;
				}
				slot.setAttachment(attachment);
				return;
			}
		}
		throw "Slot not found: " + slotName;
	},
	update: function (delta) {
		time += delta;
	}
};

spine.AttachmentType = {
	region: 0
};

spine.RegionAttachment = function () {
	this.offset = [];
	this.offset.length = 8;
	this.uvs = [];
	this.uvs.length = 8;
};
spine.RegionAttachment.prototype = {
	x: 0, y: 0,
	rotation: 0,
	scaleX: 1, scaleY: 1,
	width: 0, height: 0,
	rendererObject: null,
	regionOffsetX: 0, regionOffsetY: 0,
	regionWidth: 0, regionHeight: 0,
	regionOriginalWidth: 0, regionOriginalHeight: 0,
	setUVs: function (u, v, u2, v2, rotate) {
		var uvs = this.uvs;
		if (rotate) {
			uvs[2/*X2*/] = u;
			uvs[3/*Y2*/] = v2;
			uvs[4/*X3*/] = u;
			uvs[5/*Y3*/] = v;
			uvs[6/*X4*/] = u2;
			uvs[7/*Y4*/] = v;
			uvs[0/*X1*/] = u2;
			uvs[1/*Y1*/] = v2;
		} else {
			uvs[0/*X1*/] = u;
			uvs[1/*Y1*/] = v2;
			uvs[2/*X2*/] = u;
			uvs[3/*Y2*/] = v;
			uvs[4/*X3*/] = u2;
			uvs[5/*Y3*/] = v;
			uvs[6/*X4*/] = u2;
			uvs[7/*Y4*/] = v2;
		}
	},
	updateOffset: function () {
		var regionScaleX = this.width / this.regionOriginalWidth * this.scaleX;
		var regionScaleY = this.height / this.regionOriginalHeight * this.scaleY;
		var localX = -this.width / 2 * this.scaleX + this.regionOffsetX * regionScaleX;
		var localY = -this.height / 2 * this.scaleY + this.regionOffsetY * regionScaleY;
		var localX2 = localX + this.regionWidth * regionScaleX;
		var localY2 = localY + this.regionHeight * regionScaleY;
		var radians = this.rotation * Math.PI / 180;
		var cos = Math.cos(radians);
		var sin = Math.sin(radians);
		var localXCos = localX * cos + this.x;
		var localXSin = localX * sin;
		var localYCos = localY * cos + this.y;
		var localYSin = localY * sin;
		var localX2Cos = localX2 * cos + this.x;
		var localX2Sin = localX2 * sin;
		var localY2Cos = localY2 * cos + this.y;
		var localY2Sin = localY2 * sin;
		var offset = this.offset;
		offset[0/*X1*/] = localXCos - localYSin;
		offset[1/*Y1*/] = localYCos + localXSin;
		offset[2/*X2*/] = localXCos - localY2Sin;
		offset[3/*Y2*/] = localY2Cos + localXSin;
		offset[4/*X3*/] = localX2Cos - localY2Sin;
		offset[5/*Y3*/] = localY2Cos + localX2Sin;
		offset[6/*X4*/] = localX2Cos - localYSin;
		offset[7/*Y4*/] = localYCos + localX2Sin;
	},
	computeVertices: function (x, y, bone, vertices) {
		x += bone.worldX;
		y += bone.worldY;
		var m00 = bone.m00;
		var m01 = bone.m01;
		var m10 = bone.m10;
		var m11 = bone.m11;
		var offset = this.offset;
		vertices[0/*X1*/] = offset[0/*X1*/] * m00 + offset[1/*Y1*/] * m01 + x;
		vertices[1/*Y1*/] = offset[0/*X1*/] * m10 + offset[1/*Y1*/] * m11 + y;
		vertices[2/*X2*/] = offset[2/*X2*/] * m00 + offset[3/*Y2*/] * m01 + x;
		vertices[3/*Y2*/] = offset[2/*X2*/] * m10 + offset[3/*Y2*/] * m11 + y;
		vertices[4/*X3*/] = offset[4/*X3*/] * m00 + offset[5/*X3*/] * m01 + x;
		vertices[5/*X3*/] = offset[4/*X3*/] * m10 + offset[5/*X3*/] * m11 + y;
		vertices[6/*X4*/] = offset[6/*X4*/] * m00 + offset[7/*Y4*/] * m01 + x;
		vertices[7/*Y4*/] = offset[6/*X4*/] * m10 + offset[7/*Y4*/] * m11 + y;
	}
}

spine.AnimationStateData = function (skeletonData) {
	this.skeletonData = skeletonData;
	this.animationToMixTime = {};
};
spine.AnimationStateData.prototype = {
        defaultMix: 0,
	setMixByName: function (fromName, toName, duration) {
		var from = this.skeletonData.findAnimation(fromName);
		if (!from) throw "Animation not found: " + fromName;
		var to = this.skeletonData.findAnimation(toName);
		if (!to) throw "Animation not found: " + toName;
		this.setMix(from, to, duration);
	},
	setMix: function (from, to, duration) {
		this.animationToMixTime[from.name + ":" + to.name] = duration;
	},
	getMix: function (from, to) {
		var time = this.animationToMixTime[from.name + ":" + to.name];
            return time ? time : this.defaultMix;
	}
};

spine.AnimationState = function (stateData) {
	this.data = stateData;
	this.queue = [];
};
spine.AnimationState.prototype = {
	current: null,
	previous: null,
	currentTime: 0,
	previousTime: 0,
	currentLoop: false,
	previousLoop: false,
	mixTime: 0,
	mixDuration: 0,
	update: function (delta) {
		this.currentTime += delta;
		this.previousTime += delta;
		this.mixTime += delta;

		if (this.queue.length > 0) {
			var entry = this.queue[0];
			if (this.currentTime >= entry.delay) {
				this._setAnimation(entry.animation, entry.loop);
				this.queue.shift();
			}
		}
	},
	apply: function (skeleton) {
		if (!this.current) return;
		if (this.previous) {
			this.previous.apply(skeleton, this.previousTime, this.previousLoop);
			var alpha = this.mixTime / this.mixDuration;
			if (alpha >= 1) {
				alpha = 1;
				this.previous = null;
			}
			this.current.mix(skeleton, this.currentTime, this.currentLoop, alpha);
		} else
			this.current.apply(skeleton, this.currentTime, this.currentLoop);
	},
	clearAnimation: function () {
		this.previous = null;
		this.current = null;
		this.queue.length = 0;
	},
	_setAnimation: function (animation, loop) {
		this.previous = null;
		if (animation && this.current) {
			this.mixDuration = this.data.getMix(this.current, animation);
			if (this.mixDuration > 0) {
				this.mixTime = 0;
				this.previous = this.current;
				this.previousTime = this.currentTime;
				this.previousLoop = this.currentLoop;
			}
		}
		this.current = animation;
		this.currentLoop = loop;
		this.currentTime = 0;
	},
	/** @see #setAnimation(Animation, Boolean) */
	setAnimationByName: function (animationName, loop) {
		var animation = this.data.skeletonData.findAnimation(animationName);
		if (!animation) throw "Animation not found: " + animationName;
		this.setAnimation(animation, loop);
	},
	/** Set the current animation. Any queued animations are cleared and the current animation time is set to 0.
	 * @param animation May be null. */
	setAnimation: function (animation, loop) {
		this.queue.length = 0;
		this._setAnimation(animation, loop);
	},
	/** @see #addAnimation(Animation, Boolean, Number) */
	addAnimationByName: function (animationName, loop, delay) {
		var animation = this.data.skeletonData.findAnimation(animationName);
		if (!animation) throw "Animation not found: " + animationName;
		this.addAnimation(animation, loop, delay);
	},
	/** Adds an animation to be played delay seconds after the current or last queued animation.
	 * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */
	addAnimation: function (animation, loop, delay) {
		var entry = {};
		entry.animation = animation;
		entry.loop = loop;

		if (!delay || delay <= 0) {
			var previousAnimation = this.queue.length == 0 ? this.current : this.queue[this.queue.length - 1].animation;
			if (previousAnimation != null)
				delay = previousAnimation.duration - this.data.getMix(previousAnimation, animation) + (delay || 0);
			else
				delay = 0;
		}
		entry.delay = delay;

		this.queue.push(entry);
	},
	/** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */
	isComplete: function () {
		return !this.current || this.currentTime >= this.current.duration;
	}
};

spine.SkeletonJson = function (attachmentLoader) {
	this.attachmentLoader = attachmentLoader;
};
spine.SkeletonJson.prototype = {
	scale: 1,
	readSkeletonData: function (root) {
		var skeletonData = new spine.SkeletonData();

		// Bones.
		var bones = root["bones"];
		for (var i = 0, n = bones.length; i < n; i++) {
			var boneMap = bones[i];
			var parent = null;
			if (boneMap["parent"]) {
				parent = skeletonData.findBone(boneMap["parent"]);
				if (!parent) throw "Parent bone not found: " + boneMap["parent"];
			}
			var boneData = new spine.BoneData(boneMap["name"], parent);
			boneData.length = (boneMap["length"] || 0) * this.scale;
			boneData.x = (boneMap["x"] || 0) * this.scale;
			boneData.y = (boneMap["y"] || 0) * this.scale;
			boneData.rotation = (boneMap["rotation"] || 0);
			boneData.scaleX = boneMap["scaleX"] || 1;
			boneData.scaleY = boneMap["scaleY"] || 1;
			skeletonData.bones.push(boneData);
		}

		// Slots.
		var slots = root["slots"];
		for (var i = 0, n = slots.length; i < n; i++) {
			var slotMap = slots[i];
			var boneData = skeletonData.findBone(slotMap["bone"]);
			if (!boneData) throw "Slot bone not found: " + slotMap["bone"];
			var slotData = new spine.SlotData(slotMap["name"], boneData);

			var color = slotMap["color"];
			if (color) {
				slotData.r = spine.SkeletonJson.toColor(color, 0);
				slotData.g = spine.SkeletonJson.toColor(color, 1);
				slotData.b = spine.SkeletonJson.toColor(color, 2);
				slotData.a = spine.SkeletonJson.toColor(color, 3);
			}

			slotData.attachmentName = slotMap["attachment"];

			skeletonData.slots.push(slotData);
		}

		// Skins.
		var skins = root["skins"];
		for (var skinName in skins) {
			if (!skins.hasOwnProperty(skinName)) continue;
			var skinMap = skins[skinName];
			var skin = new spine.Skin(skinName);
			for (var slotName in skinMap) {
				if (!skinMap.hasOwnProperty(slotName)) continue;
				var slotIndex = skeletonData.findSlotIndex(slotName);
				var slotEntry = skinMap[slotName];
				for (var attachmentName in slotEntry) {
					if (!slotEntry.hasOwnProperty(attachmentName)) continue;
					var attachment = this.readAttachment(skin, attachmentName, slotEntry[attachmentName]);
					if (attachment != null) skin.addAttachment(slotIndex, attachmentName, attachment);
				}
			}
			skeletonData.skins.push(skin);
			if (skin.name == "default") skeletonData.defaultSkin = skin;
		}

		// Animations.
		var animations = root["animations"];
		for (var animationName in animations) {
			if (!animations.hasOwnProperty(animationName)) continue;
			this.readAnimation(animationName, animations[animationName], skeletonData);
		}

		return skeletonData;
	},
	readAttachment: function (skin, name, map) {
		name = map["name"] || name;

		var type = spine.AttachmentType[map["type"] || "region"];

		if (type == spine.AttachmentType.region) {
			var attachment = new spine.RegionAttachment();
			attachment.x = (map["x"] || 0) * this.scale;
			attachment.y = (map["y"] || 0) * this.scale;
			attachment.scaleX = map["scaleX"] || 1;
			attachment.scaleY = map["scaleY"] || 1;
			attachment.rotation = map["rotation"] || 0;
			attachment.width = (map["width"] || 32) * this.scale;
			attachment.height = (map["height"] || 32) * this.scale;
			attachment.updateOffset();

			attachment.rendererObject = {};
			attachment.rendererObject.name = name;
			attachment.rendererObject.scale = {};
			attachment.rendererObject.scale.x = attachment.scaleX;
			attachment.rendererObject.scale.y = attachment.scaleY;
			attachment.rendererObject.rotation = -attachment.rotation * Math.PI / 180;
			return attachment;
		}

            throw "Unknown attachment type: " + type;
	},

	readAnimation: function (name, map, skeletonData) {
		var timelines = [];
		var duration = 0;

		var bones = map["bones"];
		for (var boneName in bones) {
			if (!bones.hasOwnProperty(boneName)) continue;
			var boneIndex = skeletonData.findBoneIndex(boneName);
			if (boneIndex == -1) throw "Bone not found: " + boneName;
			var boneMap = bones[boneName];

			for (var timelineName in boneMap) {
				if (!boneMap.hasOwnProperty(timelineName)) continue;
				var values = boneMap[timelineName];
				if (timelineName == "rotate") {
					var timeline = new spine.RotateTimeline(values.length);
					timeline.boneIndex = boneIndex;

					var frameIndex = 0;
					for (var i = 0, n = values.length; i < n; i++) {
						var valueMap = values[i];
						timeline.setFrame(frameIndex, valueMap["time"], valueMap["angle"]);
						spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap);
						frameIndex++;
					}
					timelines.push(timeline);
					duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]);

				} else if (timelineName == "translate" || timelineName == "scale") {
					var timeline;
					var timelineScale = 1;
					if (timelineName == "scale")
						timeline = new spine.ScaleTimeline(values.length);
					else {
						timeline = new spine.TranslateTimeline(values.length);
						timelineScale = this.scale;
					}
					timeline.boneIndex = boneIndex;

					var frameIndex = 0;
					for (var i = 0, n = values.length; i < n; i++) {
						var valueMap = values[i];
						var x = (valueMap["x"] || 0) * timelineScale;
						var y = (valueMap["y"] || 0) * timelineScale;
						timeline.setFrame(frameIndex, valueMap["time"], x, y);
						spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap);
						frameIndex++;
					}
					timelines.push(timeline);
					duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]);

				} else
					throw "Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")";
			}
		}
		var slots = map["slots"];
		for (var slotName in slots) {
			if (!slots.hasOwnProperty(slotName)) continue;
			var slotMap = slots[slotName];
			var slotIndex = skeletonData.findSlotIndex(slotName);

			for (var timelineName in slotMap) {
				if (!slotMap.hasOwnProperty(timelineName)) continue;
				var values = slotMap[timelineName];
				if (timelineName == "color") {
					var timeline = new spine.ColorTimeline(values.length);
					timeline.slotIndex = slotIndex;

					var frameIndex = 0;
					for (var i = 0, n = values.length; i < n; i++) {
						var valueMap = values[i];
						var color = valueMap["color"];
						var r = spine.SkeletonJson.toColor(color, 0);
						var g = spine.SkeletonJson.toColor(color, 1);
						var b = spine.SkeletonJson.toColor(color, 2);
						var a = spine.SkeletonJson.toColor(color, 3);
						timeline.setFrame(frameIndex, valueMap["time"], r, g, b, a);
						spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap);
						frameIndex++;
					}
					timelines.push(timeline);
					duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 5 - 5]);

				} else if (timelineName == "attachment") {
					var timeline = new spine.AttachmentTimeline(values.length);
					timeline.slotIndex = slotIndex;

					var frameIndex = 0;
					for (var i = 0, n = values.length; i < n; i++) {
						var valueMap = values[i];
						timeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]);
					}
					timelines.push(timeline);
                        duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);

				} else
					throw "Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")";
			}
		}
		skeletonData.animations.push(new spine.Animation(name, timelines, duration));
	}
};
spine.SkeletonJson.readCurve = function (timeline, frameIndex, valueMap) {
	var curve = valueMap["curve"];
	if (!curve) return;
	if (curve == "stepped")
		timeline.curves.setStepped(frameIndex);
	else if (curve instanceof Array)
		timeline.curves.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]);
};
spine.SkeletonJson.toColor = function (hexString, colorIndex) {
	if (hexString.length != 8) throw "Color hexidecimal length must be 8, recieved: " + hexString;
	return parseInt(hexString.substring(colorIndex * 2, 2), 16) / 255;
};

spine.Atlas = function (atlasText, textureLoader) {
	this.textureLoader = textureLoader;
	this.pages = [];
	this.regions = [];

	var reader = new spine.AtlasReader(atlasText);
	var tuple = [];
	tuple.length = 4;
	var page = null;
	while (true) {
		var line = reader.readLine();
		if (line == null) break;
		line = reader.trim(line);
		if (line.length == 0)
			page = null;
		else if (!page) {
			page = new spine.AtlasPage();
			page.name = line;

			page.format = spine.Atlas.Format[reader.readValue()];

			reader.readTuple(tuple);
			page.minFilter = spine.Atlas.TextureFilter[tuple[0]];
			page.magFilter = spine.Atlas.TextureFilter[tuple[1]];

			var direction = reader.readValue();
			page.uWrap = spine.Atlas.TextureWrap.clampToEdge;
			page.vWrap = spine.Atlas.TextureWrap.clampToEdge;
			if (direction == "x")
				page.uWrap = spine.Atlas.TextureWrap.repeat;
			else if (direction == "y")
				page.vWrap = spine.Atlas.TextureWrap.repeat;
			else if (direction == "xy")
				page.uWrap = page.vWrap = spine.Atlas.TextureWrap.repeat;

			textureLoader.load(page, line);

			this.pages.push(page);

		} else {
			var region = new spine.AtlasRegion();
			region.name = line;
			region.page = page;

			region.rotate = reader.readValue() == "true";

			reader.readTuple(tuple);
			var x = parseInt(tuple[0]);
			var y = parseInt(tuple[1]);

			reader.readTuple(tuple);
			var width = parseInt(tuple[0]);
			var height = parseInt(tuple[1]);

			region.u = x / page.width;
			region.v = y / page.height;
			if (region.rotate) {
				region.u2 = (x + height) / page.width;
				region.v2 = (y + width) / page.height;
			} else {
				region.u2 = (x + width) / page.width;
				region.v2 = (y + height) / page.height;
			}
			region.x = x;
			region.y = y;
			region.width = Math.abs(width);
			region.height = Math.abs(height);

			if (reader.readTuple(tuple) == 4) { // split is optional
				region.splits = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])];

				if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits
					region.pads = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])];

					reader.readTuple(tuple);
				}
			}

			region.originalWidth = parseInt(tuple[0]);
			region.originalHeight = parseInt(tuple[1]);

			reader.readTuple(tuple);
			region.offsetX = parseInt(tuple[0]);
			region.offsetY = parseInt(tuple[1]);

			region.index = parseInt(reader.readValue());

			this.regions.push(region);
		}
	}
};
spine.Atlas.prototype = {
	findRegion: function (name) {
		var regions = this.regions;
		for (var i = 0, n = regions.length; i < n; i++)
			if (regions[i].name == name) return regions[i];
		return null;
	},
	dispose: function () {
		var pages = this.pages;
		for (var i = 0, n = pages.length; i < n; i++)
			this.textureLoader.unload(pages[i].rendererObject);
	},
	updateUVs: function (page) {
		var regions = this.regions;
		for (var i = 0, n = regions.length; i < n; i++) {
			var region = regions[i];
			if (region.page != page) continue;
			region.u = region.x / page.width;
			region.v = region.y / page.height;
			if (region.rotate) {
				region.u2 = (region.x + region.height) / page.width;
				region.v2 = (region.y + region.width) / page.height;
			} else {
				region.u2 = (region.x + region.width) / page.width;
				region.v2 = (region.y + region.height) / page.height;
			}
		}
	}
};

spine.Atlas.Format = {
	alpha: 0,
	intensity: 1,
	luminanceAlpha: 2,
	rgb565: 3,
	rgba4444: 4,
	rgb888: 5,
	rgba8888: 6
};

spine.Atlas.TextureFilter = {
	nearest: 0,
	linear: 1,
	mipMap: 2,
	mipMapNearestNearest: 3,
	mipMapLinearNearest: 4,
	mipMapNearestLinear: 5,
	mipMapLinearLinear: 6
};

spine.Atlas.TextureWrap = {
	mirroredRepeat: 0,
	clampToEdge: 1,
	repeat: 2
};

spine.AtlasPage = function () {};
spine.AtlasPage.prototype = {
	name: null,
	format: null,
	minFilter: null,
	magFilter: null,
	uWrap: null,
	vWrap: null,
	rendererObject: null,
	width: 0,
	height: 0
};

spine.AtlasRegion = function () {};
spine.AtlasRegion.prototype = {
	page: null,
	name: null,
	x: 0, y: 0,
	width: 0, height: 0,
	u: 0, v: 0, u2: 0, v2: 0,
	offsetX: 0, offsetY: 0,
	originalWidth: 0, originalHeight: 0,
	index: 0,
	rotate: false,
	splits: null,
	pads: null,
};

spine.AtlasReader = function (text) {
	this.lines = text.split(/\r\n|\r|\n/);
};
spine.AtlasReader.prototype = {
	index: 0,
	trim: function (value) {
		return value.replace(/^\s+|\s+$/g, "");
	},
	readLine: function () {
		if (this.index >= this.lines.length) return null;
		return this.lines[this.index++];
	},
	readValue: function () {
		var line = this.readLine();
		var colon = line.indexOf(":");
		if (colon == -1) throw "Invalid line: " + line;
		return this.trim(line.substring(colon + 1));
	},
	/** Returns the number of tuple values read (2 or 4). */
	readTuple: function (tuple) {
		var line = this.readLine();
		var colon = line.indexOf(":");
		if (colon == -1) throw "Invalid line: " + line;
		var i = 0, lastMatch= colon + 1;
		for (; i < 3; i++) {
			var comma = line.indexOf(",", lastMatch);
			if (comma == -1) {
				if (i == 0) throw "Invalid line: " + line;
				break;
			}
			tuple[i] = this.trim(line.substr(lastMatch, comma - lastMatch));
			lastMatch = comma + 1;
		}
		tuple[i] = this.trim(line.substring(lastMatch));
		return i + 1;
	}
}

spine.AtlasAttachmentLoader = function (atlas) {
	this.atlas = atlas;
}
spine.AtlasAttachmentLoader.prototype = {
	newAttachment: function (skin, type, name) {
		switch (type) {
		case spine.AttachmentType.region:
			var region = this.atlas.findRegion(name);
			if (!region) throw "Region not found in atlas: " + name + " (" + type + ")";
			var attachment = new spine.RegionAttachment(name);
			attachment.rendererObject = region;
			attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate);
			attachment.regionOffsetX = region.offsetX;
			attachment.regionOffsetY = region.offsetY;
			attachment.regionWidth = region.width;
			attachment.regionHeight = region.height;
			attachment.regionOriginalWidth = region.originalWidth;
			attachment.regionOriginalHeight = region.originalHeight;
			return attachment;
		}
		throw "Unknown attachment type: " + type;
	}
}

PIXI.AnimCache = {};
spine.Bone.yDown = true;

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */


/**
 * This object is one that will allow you to specify custom rendering functions based on render type
 *
 * @class CustomRenderable 
 * @extends DisplayObject
 * @constructor
 */
PIXI.CustomRenderable = function()
{
	PIXI.DisplayObject.call( this );
	
}

// constructor
PIXI.CustomRenderable.prototype = Object.create( PIXI.DisplayObject.prototype );
PIXI.CustomRenderable.prototype.constructor = PIXI.CustomRenderable;

/**
 * If this object is being rendered by a CanvasRenderer it will call this callback
 *
 * @method renderCanvas
 * @param renderer {CanvasRenderer} The renderer instance
 */
PIXI.CustomRenderable.prototype.renderCanvas = function(renderer)
{
	// override!
}

/**
 * If this object is being rendered by a WebGLRenderer it will call this callback to initialize
 *
 * @method initWebGL
 * @param renderer {WebGLRenderer} The renderer instance
 */
PIXI.CustomRenderable.prototype.initWebGL = function(renderer)
{
	// override!
}

/**
 * If this object is being rendered by a WebGLRenderer it will call this callback
 *
 * @method renderWebGL
 * @param renderer {WebGLRenderer} The renderer instance
 */
PIXI.CustomRenderable.prototype.renderWebGL = function(renderGroup, projectionMatrix)
{
	// not sure if both needed? but ya have for now!
	// override!
}


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

PIXI.BaseTextureCache = {};
PIXI.texturesToUpdate = [];
PIXI.texturesToDestroy = [];

/**
 * A texture stores the information that represents an image. All textures have a base texture
 *
 * @class BaseTexture
 * @uses EventTarget
 * @constructor
 * @param source {String} the source object (image or canvas)
 */
PIXI.BaseTexture = function(source)
{
	PIXI.EventTarget.call( this );

	/**
	 * [read-only] The width of the base texture set when the image has loaded
	 *
	 * @property width
	 * @type Number
	 * @readOnly
	 */
	this.width = 100;

	/**
	 * [read-only] The height of the base texture set when the image has loaded
	 *
	 * @property height
	 * @type Number
	 * @readOnly
	 */
	this.height = 100;

	/**
	 * [read-only] Describes if the base texture has loaded or not
	 *
	 * @property hasLoaded
	 * @type Boolean
	 * @readOnly
	 */
	this.hasLoaded = false;

	/**
	 * The source that is loaded to create the texture
	 *
	 * @property source
	 * @type Image
	 */
	this.source = source;

	if(!source)return;

	if(this.source instanceof Image || this.source instanceof HTMLImageElement)
	{
		if(this.source.complete)
		{
			this.hasLoaded = true;
			this.width = this.source.width;
			this.height = this.source.height;
			
			PIXI.texturesToUpdate.push(this);
		}
		else
		{
			
			var scope = this;
			this.source.onload = function(){
				
				scope.hasLoaded = true;
				scope.width = scope.source.width;
				scope.height = scope.source.height;
			
				// add it to somewhere...
				PIXI.texturesToUpdate.push(scope);
				scope.dispatchEvent( { type: 'loaded', content: scope } );
			}
			//	this.image.src = imageUrl;
		}
	}
	else
	{
		this.hasLoaded = true;
		this.width = this.source.width;
		this.height = this.source.height;
			
		PIXI.texturesToUpdate.push(this);
	}

	this._powerOf2 = false;
}

PIXI.BaseTexture.prototype.constructor = PIXI.BaseTexture;

/**
 * Destroys this base texture
 *
 * @method destroy
 */
PIXI.BaseTexture.prototype.destroy = function()
{
	if(this.source instanceof Image)
	{
		this.source.src = null;
	}
	this.source = null;
	PIXI.texturesToDestroy.push(this);
}

/**
 * Helper function that returns a base texture based on an image url
 * If the image is not in the base texture cache it will be  created and loaded
 *
 * @static
 * @method fromImage
 * @param imageUrl {String} The image url of the texture
 * @return BaseTexture
 */
PIXI.BaseTexture.fromImage = function(imageUrl, crossorigin)
{
	var baseTexture = PIXI.BaseTextureCache[imageUrl];
	if(!baseTexture)
	{
		// new Image() breaks tex loading in some versions of Chrome.
		// See https://code.google.com/p/chromium/issues/detail?id=238071
		var image = new Image();//document.createElement('img'); 
		if (crossorigin)
		{
			image.crossOrigin = '';
		}
		image.src = imageUrl;
		baseTexture = new PIXI.BaseTexture(image);
		PIXI.BaseTextureCache[imageUrl] = baseTexture;
	}

	return baseTexture;
}

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

PIXI.TextureCache = {};
PIXI.FrameCache = {};

/**
 * A texture stores the information that represents an image or part of an image. It cannot be added
 * to the display list directly. To do this use PIXI.Sprite. If no frame is provided then the whole image is used
 *
 * @class Texture
 * @uses EventTarget
 * @constructor
 * @param baseTexture {BaseTexture} The base texture source to create the texture from
 * @param frmae {Rectangle} The rectangle frame of the texture to show
 */
PIXI.Texture = function(baseTexture, frame)
{
	PIXI.EventTarget.call( this );

	if(!frame)
	{
		this.noFrame = true;
		frame = new PIXI.Rectangle(0,0,1,1);
	}

	if(baseTexture instanceof PIXI.Texture)
		baseTexture = baseTexture.baseTexture;

	/**
	 * The base texture of this texture
	 *
	 * @property baseTexture
	 * @type BaseTexture
	 */
	this.baseTexture = baseTexture;

	/**
	 * The frame specifies the region of the base texture that this texture uses
	 *
	 * @property frame
	 * @type Rectangle
	 */
	this.frame = frame;

	/**
	 * The trim point
	 *
	 * @property trim
	 * @type Point
	 */
	this.trim = new PIXI.Point();

	this.scope = this;

	if(baseTexture.hasLoaded)
	{
		if(this.noFrame)frame = new PIXI.Rectangle(0,0, baseTexture.width, baseTexture.height);
		//console.log(frame)
		
		this.setFrame(frame);
	}
	else
	{
		var scope = this;
		baseTexture.addEventListener( 'loaded', function(){ scope.onBaseTextureLoaded()} );
	}
}

PIXI.Texture.prototype.constructor = PIXI.Texture;

/**
 * Called when the base texture is loaded
 *
 * @method onBaseTextureLoaded
 * @param event
 * @private
 */
PIXI.Texture.prototype.onBaseTextureLoaded = function(event)
{
	var baseTexture = this.baseTexture;
	baseTexture.removeEventListener( 'loaded', this.onLoaded );

	if(this.noFrame)this.frame = new PIXI.Rectangle(0,0, baseTexture.width, baseTexture.height);
	this.noFrame = false;
	this.width = this.frame.width;
	this.height = this.frame.height;

	this.scope.dispatchEvent( { type: 'update', content: this } );
}

/**
 * Destroys this texture
 *
 * @method destroy
 * @param destroyBase {Boolean} Whether to destroy the base texture as well
 */
PIXI.Texture.prototype.destroy = function(destroyBase)
{
	if(destroyBase)this.baseTexture.destroy();
}

/**
 * Specifies the rectangle region of the baseTexture
 *
 * @method setFrame
 * @param frame {Rectangle} The frame of the texture to set it to
 */
PIXI.Texture.prototype.setFrame = function(frame)
{
	this.frame = frame;
	this.width = frame.width;
	this.height = frame.height;

	if(frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height)
	{
		throw new Error("Texture Error: frame does not fit inside the base Texture dimensions " + this);
	}

	this.updateFrame = true;

	PIXI.Texture.frameUpdates.push(this);
	//this.dispatchEvent( { type: 'update', content: this } );
}

/**
 * Helper function that returns a texture based on an image url
 * If the image is not in the texture cache it will be  created and loaded
 *
 * @static
 * @method fromImage
 * @param imageUrl {String} The image url of the texture
 * @param crossorigin {Boolean} Whether requests should be treated as crossorigin
 * @return Texture
 */
PIXI.Texture.fromImage = function(imageUrl, crossorigin)
{
	var texture = PIXI.TextureCache[imageUrl];
	
	if(!texture)
	{
		texture = new PIXI.Texture(PIXI.BaseTexture.fromImage(imageUrl, crossorigin));
		PIXI.TextureCache[imageUrl] = texture;
	}
	
	return texture;
}

/**
 * Helper function that returns a texture based on a frame id
 * If the frame id is not in the texture cache an error will be thrown
 *
 * @static
 * @method fromFrame
 * @param frameId {String} The frame id of the texture
 * @return Texture
 */
PIXI.Texture.fromFrame = function(frameId)
{
	var texture = PIXI.TextureCache[frameId];
	if(!texture)throw new Error("The frameId '"+ frameId +"' does not exist in the texture cache " + this);
	return texture;
}

/**
 * Helper function that returns a texture based on a canvas element
 * If the canvas is not in the texture cache it will be  created and loaded
 *
 * @static
 * @method fromCanvas
 * @param canvas {Canvas} The canvas element source of the texture
 * @return Texture
 */
PIXI.Texture.fromCanvas = function(canvas)
{
	var	baseTexture = new PIXI.BaseTexture(canvas);
	return new PIXI.Texture(baseTexture);
}


/**
 * Adds a texture to the textureCache.
 *
 * @static
 * @method addTextureToCache
 * @param texture {Texture}
 * @param id {String} the id that the texture will be stored against.
 */
PIXI.Texture.addTextureToCache = function(texture, id)
{
	PIXI.TextureCache[id] = texture;
}

/**
 * Remove a texture from the textureCache. 
 *
 * @static
 * @method removeTextureFromCache
 * @param id {String} the id of the texture to be removed
 * @return {Texture} the texture that was removed
 */
PIXI.Texture.removeTextureFromCache = function(id)
{
	var texture = PIXI.TextureCache[id]
	PIXI.TextureCache[id] = null;
	return texture;
}

// this is more for webGL.. it contains updated frames..
PIXI.Texture.frameUpdates = [];


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 A RenderTexture is a special texture that allows any pixi displayObject to be rendered to it.

 __Hint__: All DisplayObjects (exmpl. Sprites) that renders on RenderTexture should be preloaded. 
 Otherwise black rectangles will be drawn instead.  
 
 RenderTexture takes snapshot of DisplayObject passed to render method. If DisplayObject is passed to render method, position and rotation of it will be ignored. For example:
 
	var renderTexture = new PIXI.RenderTexture(800, 600);
	var sprite = PIXI.Sprite.fromImage("spinObj_01.png");
	sprite.position.x = 800/2;
	sprite.position.y = 600/2;
	sprite.anchor.x = 0.5;
	sprite.anchor.y = 0.5;
	renderTexture.render(sprite);

 Sprite in this case will be rendered to 0,0 position. To render this sprite at center DisplayObjectContainer should be used:

	var doc = new PIXI.DisplayObjectContainer();
	doc.addChild(sprite);
	renderTexture.render(doc);  // Renders to center of renderTexture

 @class RenderTexture
 @extends Texture
 @constructor
 @param width {Number} The width of the render texture
 @param height {Number} The height of the render texture
 */
PIXI.RenderTexture = function(width, height)
{
	PIXI.EventTarget.call( this );

	this.width = width || 100;
	this.height = height || 100;

	this.indetityMatrix = PIXI.mat3.create();

	this.frame = new PIXI.Rectangle(0, 0, this.width, this.height);	

	if(PIXI.gl)
	{
		this.initWebGL();
	}
	else
	{
		this.initCanvas();
	}
}

PIXI.RenderTexture.prototype = Object.create( PIXI.Texture.prototype );
PIXI.RenderTexture.prototype.constructor = PIXI.RenderTexture;

/**
 * Initializes the webgl data for this texture
 *
 * @method initWebGL
 * @private
 */
PIXI.RenderTexture.prototype.initWebGL = function()
{
	var gl = PIXI.gl;
	this.glFramebuffer = gl.createFramebuffer();

   	gl.bindFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer );

    this.glFramebuffer.width = this.width;
    this.glFramebuffer.height = this.height;	

	this.baseTexture = new PIXI.BaseTexture();

	this.baseTexture.width = this.width;
	this.baseTexture.height = this.height;

    this.baseTexture._glTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, this.baseTexture._glTexture);

	gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,  this.width,  this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	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);

	this.baseTexture.isRender = true;

	gl.bindFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer );
	gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.baseTexture._glTexture, 0);

	// create a projection matrix..
	this.projection = new PIXI.Point(this.width/2 , this.height/2);

	// set the correct render function..
	this.render = this.renderWebGL;

	
}


PIXI.RenderTexture.prototype.resize = function(width, height)
{

	this.width = width;
	this.height = height;
	
	if(PIXI.gl)
	{
		this.projection.x = this.width/2
		this.projection.y = this.height/2;
	
		var gl = PIXI.gl;
		gl.bindTexture(gl.TEXTURE_2D, this.baseTexture._glTexture);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,  this.width,  this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
	}
	else
	{
		
		this.frame.width = this.width
		this.frame.height = this.height;
		this.renderer.resize(this.width, this.height);
	}
}

/**
 * Initializes the canvas data for this texture
 *
 * @method initCanvas
 * @private
 */
PIXI.RenderTexture.prototype.initCanvas = function()
{
	this.renderer = new PIXI.CanvasRenderer(this.width, this.height, null, 0);

	this.baseTexture = new PIXI.BaseTexture(this.renderer.view);
	this.frame = new PIXI.Rectangle(0, 0, this.width, this.height);

	this.render = this.renderCanvas;
}

/**
 * This function will draw the display object to the texture.
 *
 * @method renderWebGL
 * @param displayObject {DisplayObject} The display object to render this texture on
 * @param clear {Boolean} If true the texture will be cleared before the displayObject is drawn
 * @private
 */
PIXI.RenderTexture.prototype.renderWebGL = function(displayObject, position, clear)
{
	var gl = PIXI.gl;

	// enable the alpha color mask..
	gl.colorMask(true, true, true, true); 

	gl.viewport(0, 0, this.width, this.height);	

	gl.bindFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer );

	if(clear)
	{
		gl.clearColor(0,0,0, 0);     
		gl.clear(gl.COLOR_BUFFER_BIT);
	}

	// THIS WILL MESS WITH HIT TESTING!
	var children = displayObject.children;

	//TODO -? create a new one??? dont think so!
	var originalWorldTransform = displayObject.worldTransform;
	displayObject.worldTransform = PIXI.mat3.create();//sthis.indetityMatrix;
	// modify to flip...
	displayObject.worldTransform[4] = -1;
	displayObject.worldTransform[5] = this.projection.y * 2;

	
	if(position)
	{
		displayObject.worldTransform[2] = position.x;
		displayObject.worldTransform[5] -= position.y;
	}
	
	PIXI.visibleCount++;
	displayObject.vcount = PIXI.visibleCount;
	
	for(var i=0,j=children.length; i<j; i++)
	{
		children[i].updateTransform();	
	}

	var renderGroup = displayObject.__renderGroup;

	if(renderGroup)
	{
		if(displayObject == renderGroup.root)
		{
			renderGroup.render(this.projection);
		}
		else
		{
			renderGroup.renderSpecific(displayObject, this.projection);
		}
	}
	else
	{
		if(!this.renderGroup)this.renderGroup = new PIXI.WebGLRenderGroup(gl);
		this.renderGroup.setRenderable(displayObject);
		this.renderGroup.render(this.projection);
	}

	displayObject.worldTransform = originalWorldTransform;
}


/**
 * This function will draw the display object to the texture.
 *
 * @method renderCanvas
 * @param displayObject {DisplayObject} The display object to render this texture on
 * @param clear {Boolean} If true the texture will be cleared before the displayObject is drawn
 * @private
 */
PIXI.RenderTexture.prototype.renderCanvas = function(displayObject, position, clear)
{
	var children = displayObject.children;

	displayObject.worldTransform = PIXI.mat3.create();
	
	if(position)
	{
		displayObject.worldTransform[2] = position.x;
		displayObject.worldTransform[5] = position.y;
	}
	

	for(var i=0,j=children.length; i<j; i++)
	{
		children[i].updateTransform();	
	}

	if(clear)this.renderer.context.clearRect(0,0, this.width, this.height);
	
    this.renderer.renderDisplayObject(displayObject);
    
    this.renderer.context.setTransform(1,0,0,1,0,0); 
    

  //  PIXI.texturesToUpdate.push(this.baseTexture);
}


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * A Class that loads a bunch of images / sprite sheet / bitmap font files. Once the
 * assets have been loaded they are added to the PIXI Texture cache and can be accessed
 * easily through PIXI.Texture.fromImage() and PIXI.Sprite.fromImage()
 * When all items have been loaded this class will dispatch a "onLoaded" event
 * As each individual item is loaded this class will dispatch a "onProgress" event
 *
 * @class AssetLoader
 * @constructor
 * @uses EventTarget
 * @param {Array<String>} assetURLs an array of image/sprite sheet urls that you would like loaded
 *      supported. Supported image formats include "jpeg", "jpg", "png", "gif". Supported
 *      sprite sheet data formats only include "JSON" at this time. Supported bitmap font
 *      data formats include "xml" and "fnt".
 * @param crossorigin {Boolean} Whether requests should be treated as crossorigin
 */
PIXI.AssetLoader = function(assetURLs, crossorigin)
{
	PIXI.EventTarget.call(this);

	/**
	 * The array of asset URLs that are going to be loaded
     *
	 * @property assetURLs
	 * @type Array<String>
	 */
	this.assetURLs = assetURLs;

    /**
     * Whether the requests should be treated as cross origin
     *
     * @property crossorigin
     * @type Boolean
     */
	this.crossorigin = crossorigin;

    /**
     * Maps file extension to loader types
     *
     * @property loadersByType
     * @type Object
     */
    this.loadersByType = {
        "jpg":  PIXI.ImageLoader,
        "jpeg": PIXI.ImageLoader,
        "png":  PIXI.ImageLoader,
        "gif":  PIXI.ImageLoader,
        "json": PIXI.JsonLoader,
        "anim": PIXI.SpineLoader,
        "xml":  PIXI.BitmapFontLoader,
        "fnt":  PIXI.BitmapFontLoader
    };
    
    
};

/**
 * Fired when an item has loaded
 * @event onProgress
 */

/**
 * Fired when all the assets have loaded
 * @event onComplete 
 */

// constructor
PIXI.AssetLoader.prototype.constructor = PIXI.AssetLoader;

/**
 * Starts loading the assets sequentially
 *
 * @method load
 */
PIXI.AssetLoader.prototype.load = function()
{
    var scope = this;

	this.loadCount = this.assetURLs.length;

    for (var i=0; i < this.assetURLs.length; i++)
	{
		var fileName = this.assetURLs[i];
		var fileType = fileName.split(".").pop().toLowerCase();

        var loaderClass = this.loadersByType[fileType];
        if(!loaderClass)
            throw new Error(fileType + " is an unsupported file type");

        var loader = new loaderClass(fileName, this.crossorigin);

        loader.addEventListener("loaded", function()
        {
            scope.onAssetLoaded();
        });
        loader.load();
	}
};

/**
 * Invoked after each file is loaded
 *
 * @method onAssetLoaded
 * @private
 */
PIXI.AssetLoader.prototype.onAssetLoaded = function()
{
    this.loadCount--;
	this.dispatchEvent({type: "onProgress", content: this});
	if(this.onProgress) this.onProgress();
	
	if(this.loadCount == 0)
	{
		this.dispatchEvent({type: "onComplete", content: this});
		if(this.onComplete) this.onComplete();
	}
};


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * The json file loader is used to load in JSON data and parsing it
 * When loaded this class will dispatch a "loaded" event
 * If load failed this class will dispatch a "error" event
 *
 * @class JsonLoader
 * @uses EventTarget
 * @constructor
 * @param url {String} The url of the JSON file
 * @param crossorigin {Boolean} Whether requests should be treated as crossorigin
 */
PIXI.JsonLoader = function (url, crossorigin) {
	PIXI.EventTarget.call(this);

	/**
	 * The url of the bitmap font data
	 *
	 * @property url
	 * @type String
	 */
	this.url = url;

	/**
	 * Whether the requests should be treated as cross origin
	 *
	 * @property crossorigin
	 * @type Boolean
	 */
	this.crossorigin = crossorigin;

	/**
	 * [read-only] The base url of the bitmap font data
	 *
	 * @property baseUrl
	 * @type String
	 * @readOnly
	 */
	this.baseUrl = url.replace(/[^\/]*$/, "");

	/**
	 * [read-only] Whether the data has loaded yet
	 *
	 * @property loaded
	 * @type Boolean
	 * @readOnly
	 */
	this.loaded = false;
	
};

// constructor
PIXI.JsonLoader.prototype.constructor = PIXI.JsonLoader;

/**
 * Loads the JSON data
 *
 * @method load
 */
PIXI.JsonLoader.prototype.load = function () {
	this.ajaxRequest = new AjaxRequest();
	var scope = this;
	this.ajaxRequest.onreadystatechange = function () {
		scope.onJSONLoaded();
	};

	this.ajaxRequest.open("GET", this.url, true);
	if (this.ajaxRequest.overrideMimeType) this.ajaxRequest.overrideMimeType("application/json");
	this.ajaxRequest.send(null);
};

/**
 * Invoke when JSON file is loaded
 *
 * @method onJSONLoaded
 * @private
 */
PIXI.JsonLoader.prototype.onJSONLoaded = function () {
	if (this.ajaxRequest.readyState == 4) {
		if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) {
			this.json = JSON.parse(this.ajaxRequest.responseText);
			
			if(this.json.frames)
			{
				// sprite sheet
				var scope = this;
				var textureUrl = this.baseUrl + this.json.meta.image;
				var image = new PIXI.ImageLoader(textureUrl, this.crossorigin);
				var frameData = this.json.frames;
			
				this.texture = image.texture.baseTexture;
				image.addEventListener("loaded", function (event) {
					scope.onLoaded();
				});
			
				for (var i in frameData) {
					var rect = frameData[i].frame;
					if (rect) {
						PIXI.TextureCache[i] = new PIXI.Texture(this.texture, {
							x: rect.x,
							y: rect.y,
							width: rect.w,
							height: rect.h
						});
						if (frameData[i].trimmed) {
							//var realSize = frameData[i].spriteSourceSize;
							PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize;
							PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w)
							// calculate the offset!
						}
					}
				}
			
				image.load();
				
			}
			else if(this.json.bones)
			{
				// spine animation
				var spineJsonParser = new spine.SkeletonJson();
				var skeletonData = spineJsonParser.readSkeletonData(this.json);
				PIXI.AnimCache[this.url] = skeletonData;
				this.onLoaded();
			}
			else
			{
				this.onLoaded();
			}
		}
		else
		{
			this.onError();
		}
	}
};

/**
 * Invoke when json file loaded
 *
 * @method onLoaded
 * @private
 */
PIXI.JsonLoader.prototype.onLoaded = function () {
	this.loaded = true;
	this.dispatchEvent({
		type: "loaded",
		content: this
	});
};

/**
 * Invoke when error occured
 *
 * @method onError
 * @private
 */
PIXI.JsonLoader.prototype.onError = function () {
	this.dispatchEvent({
		type: "error",
		content: this
	});
};
/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * The sprite sheet loader is used to load in JSON sprite sheet data
 * To generate the data you can use http://www.codeandweb.com/texturepacker and publish the "JSON" format
 * There is a free version so thats nice, although the paid version is great value for money.
 * It is highly recommended to use Sprite sheets (also know as texture atlas") as it means sprite"s can be batched and drawn together for highly increased rendering speed.
 * Once the data has been loaded the frames are stored in the PIXI texture cache and can be accessed though PIXI.Texture.fromFrameId() and PIXI.Sprite.fromFromeId()
 * This loader will also load the image file that the Spritesheet points to as well as the data.
 * When loaded this class will dispatch a "loaded" event
 *
 * @class SpriteSheetLoader
 * @uses EventTarget
 * @constructor
 * @param url {String} The url of the sprite sheet JSON file
 * @param crossorigin {Boolean} Whether requests should be treated as crossorigin
 */

PIXI.SpriteSheetLoader = function (url, crossorigin) {
	/*
	 * i use texture packer to load the assets..
	 * http://www.codeandweb.com/texturepacker
	 * make sure to set the format as "JSON"
	 */
	PIXI.EventTarget.call(this);

	/**
	 * The url of the bitmap font data
	 *
	 * @property url
	 * @type String
	 */
	this.url = url;

	/**
	 * Whether the requests should be treated as cross origin
	 *
	 * @property crossorigin
	 * @type Boolean
	 */
	this.crossorigin = crossorigin;

	/**
	 * [read-only] The base url of the bitmap font data
	 *
	 * @property baseUrl
	 * @type String
	 * @readOnly
	 */
	this.baseUrl = url.replace(/[^\/]*$/, "");

    /**
     * The texture being loaded
     *
     * @property texture
     * @type Texture
     */
    this.texture = null;

    /**
     * The frames of the sprite sheet
     *
     * @property frames
     * @type Object
     */
	this.frames = {};
};

// constructor
PIXI.SpriteSheetLoader.prototype.constructor = PIXI.SpriteSheetLoader;

/**
 * This will begin loading the JSON file
 *
 * @method load
 */
PIXI.SpriteSheetLoader.prototype.load = function () {
	var scope = this;
	var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin);
	jsonLoader.addEventListener("loaded", function (event) {
		scope.json = event.content.json;
		scope.onJSONLoaded();
	});
	jsonLoader.load();
};

/**
 * Invoke when JSON file is loaded
 *
 * @method onJSONLoaded
 * @private
 */
PIXI.SpriteSheetLoader.prototype.onJSONLoaded = function () {
	var scope = this;
	var textureUrl = this.baseUrl + this.json.meta.image;
	var image = new PIXI.ImageLoader(textureUrl, this.crossorigin);
	var frameData = this.json.frames;

	this.texture = image.texture.baseTexture;
	image.addEventListener("loaded", function (event) {
		scope.onLoaded();
	});

	for (var i in frameData) {
		var rect = frameData[i].frame;
		if (rect) {
			PIXI.TextureCache[i] = new PIXI.Texture(this.texture, {
				x: rect.x,
				y: rect.y,
				width: rect.w,
				height: rect.h
			});
			if (frameData[i].trimmed) {
				//var realSize = frameData[i].spriteSourceSize;
				PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize;
				PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w)
				// calculate the offset!
			}
		}
	}

	image.load();
};
/**
 * Invoke when all files are loaded (json and texture)
 *
 * @method onLoaded
 * @private
 */
PIXI.SpriteSheetLoader.prototype.onLoaded = function () {
	this.dispatchEvent({
		type: "loaded",
		content: this
	});
};

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * The image loader class is responsible for loading images file formats ("jpeg", "jpg", "png" and "gif")
 * Once the image has been loaded it is stored in the PIXI texture cache and can be accessed though PIXI.Texture.fromFrameId() and PIXI.Sprite.fromFromeId()
 * When loaded this class will dispatch a 'loaded' event
 *
 * @class ImageLoader
 * @uses EventTarget
 * @constructor
 * @param url {String} The url of the image
 * @param crossorigin {Boolean} Whether requests should be treated as crossorigin
 */
PIXI.ImageLoader = function(url, crossorigin)
{
    PIXI.EventTarget.call(this);

    /**
     * The texture being loaded
     *
     * @property texture
     * @type Texture
     */
    this.texture = PIXI.Texture.fromImage(url, crossorigin);
};

// constructor
PIXI.ImageLoader.prototype.constructor = PIXI.ImageLoader;

/**
 * Loads image or takes it from cache
 *
 * @method load
 */
PIXI.ImageLoader.prototype.load = function()
{
    if(!this.texture.baseTexture.hasLoaded)
    {
        var scope = this;
        this.texture.baseTexture.addEventListener("loaded", function()
        {
            scope.onLoaded();
        });
    }
    else
    {
        this.onLoaded();
    }
};

/**
 * Invoked when image file is loaded or it is already cached and ready to use
 *
 * @method onLoaded
 * @private
 */
PIXI.ImageLoader.prototype.onLoaded = function()
{
    this.dispatchEvent({type: "loaded", content: this});
};

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * The xml loader is used to load in XML bitmap font data ("xml" or "fnt")
 * To generate the data you can use http://www.angelcode.com/products/bmfont/
 * This loader will also load the image file as the data.
 * When loaded this class will dispatch a "loaded" event
 *
 * @class BitmapFontLoader
 * @uses EventTarget
 * @constructor
 * @param url {String} The url of the sprite sheet JSON file
 * @param crossorigin {Boolean} Whether requests should be treated as crossorigin
 */
PIXI.BitmapFontLoader = function(url, crossorigin)
{
    /*
     * i use texture packer to load the assets..
     * http://www.codeandweb.com/texturepacker
     * make sure to set the format as "JSON"
     */
    PIXI.EventTarget.call(this);

    /**
     * The url of the bitmap font data
     *
     * @property url
     * @type String
     */
    this.url = url;

    /**
     * Whether the requests should be treated as cross origin
     *
     * @property crossorigin
     * @type Boolean
     */
    this.crossorigin = crossorigin;

    /**
     * [read-only] The base url of the bitmap font data
     *
     * @property baseUrl
     * @type String
     * @readOnly
     */
    this.baseUrl = url.replace(/[^\/]*$/, "");

    /**
     * [read-only] The texture of the bitmap font
     *
     * @property baseUrl
     * @type String
     */
    this.texture = null;
};

// constructor
PIXI.BitmapFontLoader.prototype.constructor = PIXI.BitmapFontLoader;

/**
 * Loads the XML font data
 *
 * @method load
 */
PIXI.BitmapFontLoader.prototype.load = function()
{
    this.ajaxRequest = new XMLHttpRequest();
    var scope = this;
    this.ajaxRequest.onreadystatechange = function()
    {
        scope.onXMLLoaded();
    };

    this.ajaxRequest.open("GET", this.url, true);
    if (this.ajaxRequest.overrideMimeType) this.ajaxRequest.overrideMimeType("application/xml");
    this.ajaxRequest.send(null)
};

/**
 * Invoked when XML file is loaded, parses the data
 *
 * @method onXMLLoaded
 * @private
 */
PIXI.BitmapFontLoader.prototype.onXMLLoaded = function()
{
    if (this.ajaxRequest.readyState == 4)
    {
        if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1)
        {
            var textureUrl = this.baseUrl + this.ajaxRequest.responseXML.getElementsByTagName("page")[0].attributes.getNamedItem("file").nodeValue;
            var image = new PIXI.ImageLoader(textureUrl, this.crossorigin);
            this.texture = image.texture.baseTexture;

            var data = {};
            var info = this.ajaxRequest.responseXML.getElementsByTagName("info")[0];
            var common = this.ajaxRequest.responseXML.getElementsByTagName("common")[0];
            data.font = info.attributes.getNamedItem("face").nodeValue;
            data.size = parseInt(info.attributes.getNamedItem("size").nodeValue, 10);
            data.lineHeight = parseInt(common.attributes.getNamedItem("lineHeight").nodeValue, 10);
            data.chars = {};

            //parse letters
            var letters = this.ajaxRequest.responseXML.getElementsByTagName("char");

            for (var i = 0; i < letters.length; i++)
            {
                var charCode = parseInt(letters[i].attributes.getNamedItem("id").nodeValue, 10);

                var textureRect = {
                    x: parseInt(letters[i].attributes.getNamedItem("x").nodeValue, 10),
                    y: parseInt(letters[i].attributes.getNamedItem("y").nodeValue, 10),
                    width: parseInt(letters[i].attributes.getNamedItem("width").nodeValue, 10),
                    height: parseInt(letters[i].attributes.getNamedItem("height").nodeValue, 10)
                };
                PIXI.TextureCache[charCode] = new PIXI.Texture(this.texture, textureRect);

                data.chars[charCode] = {
                    xOffset: parseInt(letters[i].attributes.getNamedItem("xoffset").nodeValue, 10),
                    yOffset: parseInt(letters[i].attributes.getNamedItem("yoffset").nodeValue, 10),
                    xAdvance: parseInt(letters[i].attributes.getNamedItem("xadvance").nodeValue, 10),
                    kerning: {},
                    texture:new PIXI.Texture(this.texture, textureRect)

                };
            }

            //parse kernings
            var kernings = this.ajaxRequest.responseXML.getElementsByTagName("kerning");
            for (i = 0; i < kernings.length; i++)
            {
               var first = parseInt(kernings[i].attributes.getNamedItem("first").nodeValue, 10);
               var second = parseInt(kernings[i].attributes.getNamedItem("second").nodeValue, 10);
               var amount = parseInt(kernings[i].attributes.getNamedItem("amount").nodeValue, 10);

                data.chars[second].kerning[first] = amount;

            }

            PIXI.BitmapText.fonts[data.font] = data;

            var scope = this;
            image.addEventListener("loaded", function() {
                scope.onLoaded();
            });
            image.load();
        }
    }
};

/**
 * Invoked when all files are loaded (xml/fnt and texture)
 *
 * @method onLoaded
 * @private
 */
PIXI.BitmapFontLoader.prototype.onLoaded = function()
{
    this.dispatchEvent({type: "loaded", content: this});
};

/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 * based on pixi impact spine implementation made by Eemeli Kelokorpi (@ekelokorpi) https://github.com/ekelokorpi
 * 
 * Awesome JS run time provided by EsotericSoftware
 * https://github.com/EsotericSoftware/spine-runtimes
 * 
 */

/**
 * The Spine loader is used to load in JSON spine data
 * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format
 * Due to a clash of names  You will need to change the extension of the spine file from *.json to *.anim for it to load
 * See example 12 (http://www.goodboydigital.com/pixijs/examples/12/) to see a working example and check out the source
 * You will need to generate a sprite sheet to accompany the spine data 
 * When loaded this class will dispatch a "loaded" event
 *
 * @class Spine
 * @uses EventTarget
 * @constructor
 * @param url {String} The url of the JSON file
 * @param crossorigin {Boolean} Whether requests should be treated as crossorigin
 */
PIXI.SpineLoader = function(url, crossorigin) 
{
	PIXI.EventTarget.call(this);

	/**
	 * The url of the bitmap font data
	 *
	 * @property url
	 * @type String
	 */
	this.url = url;

	/**
	 * Whether the requests should be treated as cross origin
	 *
	 * @property crossorigin
	 * @type Boolean
	 */
	this.crossorigin = crossorigin;

	/**
	 * [read-only] Whether the data has loaded yet
	 *
	 * @property loaded
	 * @type Boolean
	 * @readOnly
	 */
	this.loaded = false;
}

PIXI.SpineLoader.prototype.constructor = PIXI.SpineLoader;

/**
 * Loads the JSON data
 *
 * @method load
 */
PIXI.SpineLoader.prototype.load = function () {
	
	var scope = this;
	var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin);
	jsonLoader.addEventListener("loaded", function (event) {
		scope.json = event.content.json;
		scope.onJSONLoaded();
	});
	jsonLoader.load();
};

/**
 * Invoke when JSON file is loaded
 *
 * @method onJSONLoaded
 * @private
 */
PIXI.SpineLoader.prototype.onJSONLoaded = function (event) {
	var spineJsonParser = new spine.SkeletonJson();
	var skeletonData = spineJsonParser.readSkeletonData(this.json);
	
	PIXI.AnimCache[this.url] = skeletonData;

	this.onLoaded();
};

/**
 * Invoke when JSON file is loaded
 *
 * @method onLoaded
 * @private
 */
PIXI.SpineLoader.prototype.onLoaded = function () {
	this.loaded = true;
    this.dispatchEvent({type: "loaded", content: this});
};


/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

 if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = PIXI;
    }
    exports.PIXI = PIXI;
  } else {
    root.PIXI = PIXI;
  }


}).call(this);
define("pixi", (function (global) {
    return function () {
        var ret, fn;
        return ret || global.PIXI;
    };
}(this)));

/*global $, define: false, d3: false */
// ------------------------------------------------------------
//
//   SVG View Container
//
// ------------------------------------------------------------
define('common/views/svg-container',['require','common/performance','common/benchmark/benchmark','common/views/tab-index','common/views/hit-testing-helper','common/console','pixi'],function (require) {
  // Dependencies.
  var performance           = require('common/performance'),
      benchmark             = require('common/benchmark/benchmark'),
      getNextTabIndex       = require('common/views/tab-index'),
      HitTestingHelper      = require('common/views/hit-testing-helper'),
      console               = require('common/console'),
      PIXI                  = require('pixi'),

      CANVAS_OVERSAMPLING = 2,

      MAX_Z_INDEX = 1000;

    // Assume that we can have *only one* Pixi renderer.
    // This is caused by the Pixi bug: https://github.com/GoodBoyDigital/pixi.js/issues/181
    function getPixiRenderer(w, h) {
      if (getPixiRenderer.instance == null) {
        var browser = benchmark.browser;
        var newRenderer;
        if (browser.browser === 'Firefox' && browser.oscpu.match(/Mac OS X 10.6/)) {
          // Work around GPU driver brokenness on some hardware running OS X 10.6 by not using
          // WebGL. Note Chrome automatically disables WebGL when using the problematic driver.
          // (Note that sometimes the separator between 10 and 6 is a '.' and sometimes a '_' so
          // use of the '.' matcher works is required)
          newRenderer = function(w, h, view, transparent) {
            return new PIXI.CanvasRenderer(w, h, view, transparent);
          };
        } else {
          newRenderer = PIXI.autoDetectRenderer;
        }
        getPixiRenderer.instance = newRenderer(w * CANVAS_OVERSAMPLING, h * CANVAS_OVERSAMPLING, null, true);
      } else {
        getPixiRenderer.instance.resize(w, h);
      }
      return getPixiRenderer.instance;
    }
    getPixiRenderer.instance = null;

  return function SVGContainer(model, modelUrl, Renderer, opt) {
        // Public API object to be returned.
    var api,

        i18n = opt && opt.i18n || null,

        // Coordinate system origin. Supported values are 'bottom-left' and 'top-left'.
        origin = opt && opt.origin || 'bottom-left',

        $el,
        node,

        plotContainer, backgroundContainer, foregroundContainer,

        backgroundRect, backgroundGroup, foregroundGroup, brushContainer,

        pixiRenderers, pixiStages, pixiContainers,

        hitTestingHelper,
        viewportZIndex = 0,

        cx, cy,
        viewport, viewPortZoom,

        model2canvas    = d3.scale.linear(),
        model2canvasInv = d3.scale.linear(),

        // Basic scaling functions for position, it transforms model units to "pixels".
        // Use it for positions of objects rendered inside the view.
        //
        // This function is exposed in public API. Never ever recreated it, as
        // renderers and sub-renders will loose reference to valid scale
        // function.
        model2px = d3.scale.linear(),

        // Inverted scaling function for position transforming model units to "pixels".
        // Use it for Y coordinates, as Y axis in model coordinate system increases
        // from bottom to top, while but SVG has increases from top to bottom.
        //
        // This function is exposed in public API. Never ever recreated it, as
        // renderers and sub-renders will loose reference to valid scale
        // function.
        model2pxInv = d3.scale.linear(),

        clickHandler,
        dragHandler,
        // d3.svg.brush object used to implement select action. It should be
        // updated each time model2px and model2pxInv functions are changed!
        selectBrush,

        dispatch = d3.dispatch("viewportDrag"),

        renderer;

    function nextViewportZIndex() {
      return viewportZIndex++;
    }

    function getFontSizeInPixels() {
      return parseFloat($el.css('font-size')) || 18;
    }

    function scale() {
      var viewPortWidth = model.get("viewPortWidth"),
          viewPortHeight = model.get("viewPortHeight"),
          viewPortX = model.get("viewPortX"),
          viewPortY = model.get("viewPortY"),
          aspectRatio, modelSize;

      viewPortZoom = model.get("viewPortZoom") || 1;

      // Model size in model units.
      modelSize = {
        "minX": model.get('minX'),
        "minY": model.get('minY'),
        "maxX": model.get('maxX'),
        "maxY": model.get('maxY')
      };

      // Note that viewPort specification can be undefined and then viewport
      // should fit the model.
      viewport = {
        width: viewPortWidth != null ? viewPortWidth : modelSize.maxX - modelSize.minX,
        height: viewPortHeight != null ? viewPortHeight : modelSize.maxY - modelSize.minY,
        x: viewPortX != null ? viewPortX : modelSize.minX,
        y: viewPortY != null ? viewPortY : modelSize.minY
      };

      viewport.scaledWidth  = viewport.width / viewPortZoom;
      viewport.scaledHeight = viewport.height / viewPortZoom;
      if (origin === 'bottom-left') {
        viewport.y += viewport.scaledHeight;
      }

      aspectRatio = viewport.width / viewport.height;

      cx = $el.width();
      cy = cx / aspectRatio;
      node.style.height = cy + "px";

      // Basic model2px scaling function for position.
      model2px
        .domain([0, viewport.width])
        .range([0, cx]);

      model2canvas
        .domain([0, viewport.scaledWidth])
        .range([0, cx * CANVAS_OVERSAMPLING]);

      // Inverted model2px scaling function for position (for y-coordinates, domain can be inverted).
      model2pxInv
        .domain([viewport.height, 0])
        .range(origin === 'bottom-left' ? [0, cy] : [cy, 0]);

      model2canvasInv
        .domain([viewport.scaledHeight, 0])
        .range(origin === 'bottom-left' ? [0, cy * CANVAS_OVERSAMPLING] :
                                          [cy * CANVAS_OVERSAMPLING, 0]);

      if (selectBrush) {
        // Update brush to use new scaling functions.
        selectBrush
          .x(model2px)
          .y(model2pxInv);
      }
    }

    function redrawGridLinesAndLabels() {
      var fsize = 0.7 * getFontSizeInPixels(),
          // Overwrite default model2px and model2pxInv to display correct units.
          model2px = d3.scale.linear().domain([viewport.x + 0.07 * viewport.scaledWidth, viewport.x + viewport.scaledWidth]).range([0.07 * cx, cx]),
          model2pxInv = d3.scale.linear().domain([viewport.y, viewport.y - 0.93 * viewport.scaledHeight]).range([0, 0.93 * cy]),
          tx = function(d) { return "translate(" + model2px(d) + ",0)"; },
          ty = function(d) { return "translate(0," + model2pxInv(d) + ")"; },
          stroke = function(d) { return d ? "#ccc" : "#666"; },
          fx = model2px.tickFormat(5),
          fy = model2pxInv.tickFormat(5),
          lengthUnits = model.getUnitDefinition ? model.getUnitDefinition('length') : "",
          drawXunits = model.get("xunits"),
          drawYunits = model.get("yunits"),
          drawXLabel = model.get("xlabel"),
          drawYLabel = model.get("ylabel"),
          xlabel,
          ylabel;

      if (d3.event && d3.event.transform) {
        d3.event.transform(model2px, model2pxInv);
      }

      plotContainer.selectAll("g.x, g.y").remove();

      // Regenerate x-ticks…
      var gx = plotContainer.selectAll("g.x")
          .data(model2px.ticks(5), String)
          .attr("transform", tx)
          .classed("axes", true);

      gx.select("text").text(fx);

      var gxe = gx.enter().append("g")
          .attr("class", "x")
          .attr("transform", tx);

      if (model.get("gridLines")) {
        gxe.append("line")
            .attr("stroke", stroke)
            .attr("y1", 0)
            .attr("y2", cy - (drawXLabel ? fsize : 0) - (drawXunits ? fsize : 0));
      } else {
        gxe.selectAll("line").remove();
      }

      // x-axis label
      xlabel = plotContainer.selectAll("text.xlabel").data(drawXLabel ? [lengthUnits.pluralName] : []);
      xlabel.enter().append("text");
      xlabel
          .attr("class", "axis")
          .attr("class", "xlabel")
          .attr("x", cx / 2)
          .attr("y", cy)
          .attr("dy", "-0.1em")
          .style("text-anchor", "middle")
          .text(String);
      xlabel.exit().remove();

      // x-axis units
      if (drawXunits) {
        gxe.append("text")
            .attr("class", "xunits")
            .attr("y", cy)
            .attr("dy", model.get("xlabel") ? "-1em" : "-0.1em")
            .attr("text-anchor", "middle")
            .text(fx);
      } else {
        gxe.select("text.xunits").remove();
      }

      // Regenerate y-ticks…
      var gy = plotContainer.selectAll("g.y")
          .data(model2pxInv.ticks(5), String)
          .attr("transform", ty)
          .classed("axes", true);

      gy.select("text")
          .text(fy);

      var gye = gy.enter().append("g")
          .attr("class", "y")
          .attr("transform", ty)
          .attr("background-fill", "#FFEEB6");

      if (model.get("gridLines")) {
        gye.append("line")
            .attr("stroke", stroke)
            .attr("x1", (drawYLabel ? fsize : 0) + (drawYunits ? 2 * fsize : 0))
            .attr("x2", cx);
      } else {
        gye.selectAll("line").remove();
      }

      // y-axis label
      ylabel = plotContainer.selectAll("text.ylabel").data(drawYLabel ? [lengthUnits.pluralName] : []);
      ylabel.enter().append("text");
      ylabel
          .attr("class", "axis")
          .attr("class", "ylabel")
          .attr("transform","translate(0 " + (cy * 0.5) + ") rotate(-90)")
          .attr("dy", "0.75em")
          .style("text-anchor","middle")
          .text(String);
      ylabel.exit().remove();

      // y-axis units
      if (drawYunits) {
        gye.append("text")
            .attr("class", "yunits")
            .attr("dy", "0.34em")
            .attr("dx", model.get("ylabel") ? "1em" : "0.1em")
            .text(fy);
      } else {
        gxe.select("text.yunits").remove();
      }
    }

    // Setup background.
    function setupBackground() {
      var color = model.get("backgroundColor") || "rgba(0, 0, 0, 0)";
      backgroundRect.attr("fill", color);
      // Set color of PIXI.Stage to fix an issue with outlines around the objects that are visible
      // when WebGL renderer is being used. It only happens when PIXI.Stage background is different
      // from model container background. It's necessary to convert color into number, as PIXI
      // accepts only numbers. D3 helps us handle color names like "red", "green" etc. It doesn't
      // support rgba values, so ingore alpha channel.
      pixiStages.forEach(function (pixiStage) {
        pixiStage.setBackgroundColor(parseInt(d3.rgb(color.replace("rgba", "rgb")).toString().substr(1), 16));
      });
    }

    function mousedown() {
      setFocus();
    }

    function setFocus() {
      if (model.get("enableKeyboardHandlers")) {
        node.focus();
      }
    }

    function basicSVGAttrs() {
      return this.attr({
        // TODO confirm xmlns def is required?
        'xmlns': 'http://www.w3.org/2000/svg',
        'xmlns:xmlns:xlink': 'http://www.w3.org/1999/xlink', // hack: doubling xmlns: so it doesn't disappear once in the DOM
        'overflow': 'hidden' // Important in IE! Otherwise content won't be clipped by SVG container
      });
    }

    function layeredOnTop() {
      return this.style({
        position: "absolute",
        top: 0,
        left: 0
      });
    }

    function renderContainer() {
      var viewBox;

      // Create container, or update properties if it already exists.
      if (plotContainer === undefined) {

        plotContainer = d3.select(node).append("svg")
          .attr("class", "root-layer container plot-container")
          .style("z-index", nextViewportZIndex())
          .call(basicSVGAttrs);

        backgroundRect = plotContainer.append("rect")
          .attr("class", "container-background background");

        backgroundContainer = d3.select(node).append("svg")
          .attr("class", "root-layer container background-container svg-viewport")
          .style("z-index", nextViewportZIndex())
          .call(basicSVGAttrs);

        backgroundGroup = backgroundContainer.append("g");

        foregroundContainer = d3.select(node).append("svg")
          .attr("class", "root-layer container foreground-container svg-viewport")
          .style("z-index", MAX_Z_INDEX)
          // IE bug: without background color the layer will be transparent for mouse events
          // when there is some underlying canvas. See:
          // https://www.pivotaltracker.com/story/show/58418116
          .style("background-color", "rgba(0,0,0,0)")
          .on("contextmenu", function() {
            // Disable default context menu on foreground container, as otherwise it  covers all
            // possible context menu that can be used by layers beneath.
            d3.event.preventDefault();
          })
          .call(basicSVGAttrs);

        foregroundGroup = foregroundContainer.append("g");

        brushContainer = foregroundContainer.append("g")
          .attr("class", "brush-container");

        // Root layers should overlap each other.
        d3.select(node).selectAll(".root-layer").call(layeredOnTop);

        if (model.get("enableKeyboardHandlers")) {
          d3.select(node)
            .attr("tabindex", 0)
            .on("mousedown", mousedown);
        }

        pixiRenderers = [];
        pixiStages = [];
        pixiContainers = [];

        // Setup custom hit testing similar to one provided natively by SVG. So layers can overlap,
        // but only real objects (elements inside layer) will block mouse events.
        hitTestingHelper = new HitTestingHelper(foregroundContainer.node());
        hitTestingHelper.addLayer(plotContainer.node());
        hitTestingHelper.addLayer(backgroundContainer.node());
      }

      // Update cx, cy, size, viewport and modelSize variables.
      scale();

      // Dimension/position of all the root layers
      d3.select(node).selectAll('.root-layer')
        .attr({
          width: cx,
          height: cy
        })
        // Update style values too, as otherwise SVG isn't clipped correctly e.g. in Safari.
        .style({
          width: cx + "px",
          height: cy + "px"
        });

      pixiRenderers.forEach(function (pixiRenderer) {
        pixiRenderer.resize(cx * CANVAS_OVERSAMPLING, cy * CANVAS_OVERSAMPLING);
        $(pixiRenderer.view).css({
          width: cx,
          height: cy
        });
      });

      viewBox = model2px(viewport.x) + " " +
                model2pxInv(viewport.y) + " " +
                model2px(viewport.scaledWidth) + " " +
                model2px(viewport.scaledHeight);

      // Apply the viewbox to all "viewport" layers we have created
      d3.select(node).selectAll(".svg-viewport").attr({
        viewBox: viewBox,
        x: 0,
        y: 0,
        width: model2px(viewport.width),
        height: model2px(viewport.height)
      });

      pixiContainers.forEach(function (pixiContainer) {
        // It would be nice to set position of PIXI.Stage object, but it doesn't work. We have
        // to use nested PIXI.DisplayObjectContainer:
        pixiContainer.pivot.x = model2canvas(viewport.x);
        pixiContainer.pivot.y = model2canvasInv(viewport.y);
        // This would also work:
        // pixiContainer.scale.x = pixiContainer.scale.y = (modelSize.maxX - modelSize.minX) /
        //                                                  viewport.scaledWidth;
        // and would be pretty fast, however sprites will be pixelated. To ensure that quality isn't
        // affected it's better to modify .model2canvas() functions.
      });

      // Update padding, as it can be changed after rescaling.
      // TODO move this up to where other attrs are set on 'layers'. It doesn't look like 'padding'
      // is changed between here and there (and if it *is*, that needs to be made more explicit.)

      // Rescale main plot.
      backgroundRect
        .attr({
          width: model2px(viewport.width),
          height: model2px(viewport.height),
          x: 0,
          y: 0
        });

      redrawGridLinesAndLabels();
      api.renderCanvas();
    }

    // Support viewport dragging behavior.
    function viewportDragging() {
      var xs = [],
          ys = [],
          ts = [],
          samples = 8,
          newDrag = false,
          dragOpt = model.properties.viewPortDrag || false,
          vx, vy, t,
          dragBehavior;

      if (dragOpt === false) {
        // This causes that drag behavior will be removed and dragging of
        // other nodes will work again. It's based on the d3 implementation,
        // please see drag() function here:
        // https://github.com/mbostock/d3/blob/master/src/behavior/drag.js
        plotContainer.on("mousedown.drag", null)
            .on("touchstart.drag", null)
            .classed("draggable", false);
        return;
      }

      dragBehavior = d3.behavior.drag();
      dragBehavior.on("dragstart", function () {
        newDrag = true;
        xs.length = 0;
        ys.length = 0;
        ts.length = 0;
        updateArrays();

        // Prevent default on mousemove. It's necessary when we deal with synthetic mouse
        // events translated from touch events. Then we have to prevent default action (panning,
        // zooming etc.).
        d3.select(window).on("mousemove.viewport-drag", function () {
          d3.event.preventDefault();
        });
      }).on("drag", function () {
        var dx = dragOpt === "y" ? 0 : model2px.invert(d3.event.dx),
            dy = dragOpt === "x" ? 0 : model2px.invert(d3.event.dy);
        model.properties.viewPortX -= dx;
        model.properties.viewPortY += dy;
        dispatch.viewportDrag();
        updateArrays();
      }).on("dragend", function () {
        d3.select(window).on("mousemove.viewport-drag", null);

        updateArrays();
        var last = xs.length - 1,
            dt = ts[last] - ts[0];
        // Prevent from division by 0.
        if (dt < 1e-5) return;
        // When time difference between last 'drag' and 'dragend' events is
        // bigger than 100ms assume that there should be no interia (it means
        // that pointer was staying in one place > 100ms just before 'mouseup').
        if (ts[last] - ts[last - 1] > 100) return;
        vx = (xs[last] - xs[0]) / dt;
        vy = (ys[last] - ys[0]) / dt;
        t  = ts[last];
        newDrag = false;
        d3.timer(step);
      });

      plotContainer.call(dragBehavior).classed("draggable", true);

      function updateArrays() {
        xs.push(model.properties.viewPortX);
        ys.push(model.properties.viewPortY);
        ts.push(performance.now());
        if(xs.length > samples) {
          xs.shift();
          ys.shift();
          ts.shift();
        }
      }

      function step() {
        if (newDrag) return true;

        var now = performance.now(),
            dt = now - t,
            ax = -0.003 * vx,
            ay = -0.003 * vy;

        // Update positions.
        model.properties.viewPortX += vx * dt + 0.5 * ax * dt * dt;
        model.properties.viewPortY += vy * dt + 0.5 * ay * dt * dt;
        dispatch.viewportDrag();
        // Update velocities.
        vx += ax * dt;
        vy += ay * dt;
        // Update last time.
        t = now;

        if (Math.abs(vx) < 1e-5 && Math.abs(vy) < 1e-5) {
          return true;
        }
        return false;
      }
    }

    function removeClickHandlers() {
      var selector;
      for (selector in clickHandler) {
        if (clickHandler.hasOwnProperty(selector)) {
          plotContainer.selectAll(selector).on("click.custom", null);
        }
      }
    }

    function init() {
      // Setup model view state.
      renderContainer();
      viewportDragging();

      clickHandler = {};
      dragHandler = {};

      // Register listeners.
      // Redraw container each time when some visual-related property is changed.
      model.addPropertiesListener([ "backgroundColor"], api.repaint);
      model.addPropertiesListener(["gridLines", "xunits", "yunits", "xlabel", "ylabel",
                                   "viewPortX", "viewPortY", "viewPortZoom"],
                                   renderContainer);
      model.addPropertiesListener(["viewPortDrag"],
                                   viewportDragging);

    }

    api = {
      get $el() {
        return $el;
      },
      get node() {
        return node;
      },
      get foregroundContainer() {
        return foregroundContainer;
      },
      get model2px() {
        return model2px;
      },
      get model2canvas() {
        return model2canvas;
      },
      get model2pxInv() {
        return model2pxInv;
      },
      get model2canvasInv() {
        return model2canvasInv;
      },
      get i18n() {
        return i18n;
      },
      get setFocus() {
        return setFocus;
      },
      get fontFamily() {
        // Note that we intentionally return font-family of the model container instead of
        // the interactive container. Custom theme may apply some font to interactive container,
        // but it may want to keep fonts inside model unchanged.
        return this.$el.css('font-family');
      },
      get getFontSizeInPixels() {
        return getFontSizeInPixels;
      },
      get url() {
        return modelUrl;
      },
      get clickHandler() {
        return clickHandler;
      },
      get dragHandler() {
        return dragHandler;
      },

      get hitTestCallback() {
        return hitTestingHelper.hitTestCallback;
      },

      get mouseupCallback() {
        return hitTestingHelper.mouseupCallback;
      },

      repaint: function() {
        setupBackground();
        if (renderer.repaint) renderer.repaint();

        api.updateClickHandlers();

        api.renderCanvas();
      },

      /**
        Renderers call this method to append a "viewport" svg <g> element on behalf of a renderer.

        Viewport svgs are drawn to the exact same dimensions at the exact same screen coordinates
        (they overlap each other exactly.) Viewports added later are drawn above viewports added
        earlier, but are transparent.

        Viewports can contain layering <g> elements;

        What makes the viewports special is that their viewBox attribute is automatically adjusted
        when the model viewport (visible part of the model) is adjusted. Renderers can just draw to
        the viewport element without needing to think about

        Viewports are added in front of all viewports previously added. At the moment, they cannot
        be reordered.
      */
      appendViewport: function() {
        var parent = pixiRenderers.length > 0 ? foregroundGroup : backgroundGroup;
        return parent.append("g");
      },

      /**
        Please see .appendViewport() docs.
        The main difference is that it returns PIXI.DisplayObjectContainer object and related
        canvas (where container will be rendered) instead of SVG group element.

        Note that mousemove events will be always passed to this viewport.
       */
      appendPixiViewport: function() {
        var pixiRenderer, pixiStage;
        var browser;
        var newRenderer;

        if (pixiRenderers.length === 0) {
          pixiRenderer = getPixiRenderer(cx, cy);
          pixiStage = new PIXI.Stage(null);

          node.appendChild(pixiRenderer.view);
          d3.select(pixiRenderer.view)
            .attr("class", "pixi-viewport")
            .style("z-index", nextViewportZIndex())
            .call(layeredOnTop);

          pixiRenderers.push(pixiRenderer);
          pixiStages.push(pixiStage);

          // Cascade events into this viewport.
          hitTestingHelper.addLayer(pixiRenderer.view);
          hitTestingHelper.passMouseMove(foregroundContainer.node(), pixiRenderers[0].view);
        }

        var pixiContainer = new PIXI.DisplayObjectContainer();
        pixiStages[0].addChild(pixiContainer);
        pixiContainers.push(pixiContainer);

        // We return container instead of stage, as we can apply view port transformations to it.
        // Stage transformations seem to be ignored by the PIXI renderer.
        return {
          pixiContainer: pixiContainer,
          canvas: pixiRenderers[0].view
        };
      },

      resize: function() {
        renderContainer();
        api.repaint();

        if (selectBrush) {
          brushContainer.select("g.select-area").call(selectBrush);
        }

        if (renderer.resize) renderer.resize();

        api.renderCanvas();
      },

      setup: function() {
        if (renderer.setup) renderer.setup(model);

        api.renderCanvas();
      },

      update: function() {
        if (renderer.update) renderer.update();

        api.renderCanvas();
      },

      renderCanvas: function() {
        var i, len;
        // For now we follow that each Pixi viewport has just one PIXI.Stage.
        for (i = 0, len = pixiRenderers.length; i < len; i++) {
          pixiRenderers[i].render(pixiStages[i]);
        }
      },

      getHeightForWidth: function (width) {
        var aspectRatio = viewport.width / viewport.height;
        return width / aspectRatio;
      },

      bindModel: function(newModel, newModelUrl) {
        modelUrl = newModelUrl || modelUrl;
        model = newModel || model;
        removeClickHandlers();
        api.setSelectHandler(null);
        init();

        if (renderer.bindModel) renderer.bindModel(newModel, newModelUrl);
      },

      pos: function() {
        // Add a pos() function so the model renderer can more easily
        // manipulate absolutely positioned dom elements it may create or
        // manage.
        var rect = {
              bottom: 0,
              top:    0,
              height: 0,
              left:   0,
              right:  0,
              width:  0
            };
        if ($el) {
          rect.top = $el.position().top;
          rect.left = $el.position().left;
          rect.width = $el.width();
          rect.height = $el.height();
          rect.bottom = rect.top + rect.height;
          rect.right = rect.left + rect.width;
        }
        return rect;
      },

      on: function(type, listener) {
        dispatch.on(type, listener);
      },

      /**
       * Sets custom click handler.
       *
       * @param {string}   selector Selector string defining clickable objects.
       * @param {Function} handler  Custom click handler. It will be called
       *                            when object is clicked with (x, y, d, i) arguments:
       *                              x - x coordinate in model units,
       *                              y - y coordinate in model units,
       *                              d - data associated with a given object (can be undefined!),
       *                              i - ID of clicked object (usually its value makes sense if d is defined).
       */
      setClickHandler: function (selector, handler) {
        if (typeof handler !== "function") {
          throw new Error("Click handler should be a function.");
        }
        clickHandler[selector] = handler;
        api.updateClickHandlers();
      },

      /**
       * Applies all custom click handlers to objects matching selector
       * Note that this function should be called each time when possibly
       * clickable object is added or repainted!
       */
      updateClickHandlers: function () {
        var selector;

        function getClickHandler (handler) {
          return function (d, i) {
            if (d3.event.defaultPrevented) return;
            // Get current coordinates relative to the plot area!
            var coords = d3.mouse(backgroundRect.node()),
                x = model2px.invert(coords[0]),
                y = model2pxInv.invert(coords[1]);
            console.log("[view] click at (" + x.toFixed(3) + ", " + y.toFixed(3) + ")");
            handler(x, y, d, i);
          };
        }

        for (selector in clickHandler) {
          if (clickHandler.hasOwnProperty(selector)) {
            // Use 'custom' namespace to don't overwrite other click handlers which
            // can be added by default.
            d3.selectAll(selector).on("click.custom", getClickHandler(clickHandler[selector]));
          }
        }
      },

      /**
       * Sets custom select handler. When you provide function as a handler, select action
       * is enabled and the provided handler executed when select action is finished.
       * To disable select action, pass 'null' as an argument.
       *
       * @param {Function} handler Custom select handler. It will be called
       *                           when select action is finished with (x, y, w, h) arguments:
       *                             x - x coordinate of lower left selection corner (in model units),
       *                             y - y coordinate of lower left selection corner (in model units),
       *                             width  - width of selection rectangle (in model units),
       *                             height - height of selection rectangle (in model units).
       *
       *                            Pass 'null' to disable select action.
       */
      setSelectHandler: function (handler) {
        if (typeof handler !== "function" && handler !== null) {
          throw new Error("Select handler should be a function or null.");
        }
        // Remove previous select handler.
        brushContainer.select("g.select-area").remove();
        if (handler === null) {
          // Previous handler removed, so just return.
          selectBrush = null;
          return;
        }
        selectBrush = d3.svg.brush()
          .x(model2px)
          .y(model2pxInv)
          .on("brushstart.select", function() {
            // Prevent default on mousemove. It's necessary when we deal with synthetic mouse
            // events translated from touch events. Then we have to prevent default action (panning,
            // zooming etc.).
            d3.select(window).on("mousemove.select", function () {
              d3.event.preventDefault();
            });
          })
          .on("brushend.select", function() {
            d3.select(window).on("mousemove.select", null);

            var r = selectBrush.extent(),
                x      = r[0][0],
                y      = r[0][1],
                width  = r[1][0] - x,
                height = r[1][1] - y;

            console.log("[view] selection area (" + x.toFixed(3) + ", " +
              y.toFixed(3) + "), width: " + width + ", height: " + height);

            // Call the user defined callback, passing selected area, as
            // rectangle defined by:
            // x, y, width, height
            // where (x, y) defines its lower left corner in model units.
            handler(x, y, width, height);
            // Clear and hide the brush.
            selectBrush.clear();
            // Redraw brush (which is now empty).
            brushContainer.select("g.select-area").call(selectBrush);
          });
        // Add a new "g" to easily remove it while
        // disabling / reseting select action.
        brushContainer.append("g").classed("select-area", true).call(selectBrush);
      },
      /**
       * Sets custom drag handler. Note that dragging behavior is very specific for implementation
       * and it's done in the particular renderers. That's why this functions only provides handler
       * for renderers in .dragHandler property (plain object). Renderers that implement dragging
       * behavior can tests whether drag handler for a given object type is available, e.g.:
       * if (svgContainer.dragHandler.someObject) {
       *   svgContainer.dragHandler.someObject(x, y, d, i);
       * }
       * This method is mostly about convention, it doesn't provide any special behavior.
       *
       * @param {string}   selector String defining draggable objects.
       * @param {Function} handler  Custom drag handler. It will be called
       *                            when object is dragged with (x, y, d, i) arguments:
       *                              x - x coordinate in model units,
       *                              y - y coordinate in model units,
       *                              d - data associated with a given object (can be undefined!),
       *                              i - ID of an object (usually its value makes sense if d is defined).
       */
      setDragHandler: function (type, handler) {
        dragHandler[type] = handler;
      }
    };

    // Initialization.
    // jQuery object with model container.
    $el = $("<div>")
      .attr({
        "id": "model-container",
        "class": "container",
        "tabindex": getNextTabIndex
      })
      // Set initial dimensions.
      .css({
        "width": "50px",
        "height": "50px"
      });
    // DOM element.
    node = $el[0];

    // REF TODO ugly
    if (model) {
      init();
    }
    renderer = new Renderer(api, model);

    return api;
  };
});

/**
 * A class to parse color values
 * @author Stoyan Stefanov <sstoo@gmail.com>
 * @link   http://www.phpied.com/rgb-color-parser-in-javascript/
 * @license Use it if you like it
 */
function RGBColor(color_string)
{
    this.ok = false;

    // strip any leading #
    if (color_string.charAt(0) == '#') { // remove # if any
        color_string = color_string.substr(1,6);
    }

    color_string = color_string.replace(/ /g,'');
    color_string = color_string.toLowerCase();

    // before getting into regexps, try simple matches
    // and overwrite the input
    var simple_colors = {
        aliceblue: 'f0f8ff',
        antiquewhite: 'faebd7',
        aqua: '00ffff',
        aquamarine: '7fffd4',
        azure: 'f0ffff',
        beige: 'f5f5dc',
        bisque: 'ffe4c4',
        black: '000000',
        blanchedalmond: 'ffebcd',
        blue: '0000ff',
        blueviolet: '8a2be2',
        brown: 'a52a2a',
        burlywood: 'deb887',
        cadetblue: '5f9ea0',
        chartreuse: '7fff00',
        chocolate: 'd2691e',
        coral: 'ff7f50',
        cornflowerblue: '6495ed',
        cornsilk: 'fff8dc',
        crimson: 'dc143c',
        cyan: '00ffff',
        darkblue: '00008b',
        darkcyan: '008b8b',
        darkgoldenrod: 'b8860b',
        darkgray: 'a9a9a9',
        darkgreen: '006400',
        darkkhaki: 'bdb76b',
        darkmagenta: '8b008b',
        darkolivegreen: '556b2f',
        darkorange: 'ff8c00',
        darkorchid: '9932cc',
        darkred: '8b0000',
        darksalmon: 'e9967a',
        darkseagreen: '8fbc8f',
        darkslateblue: '483d8b',
        darkslategray: '2f4f4f',
        darkturquoise: '00ced1',
        darkviolet: '9400d3',
        deeppink: 'ff1493',
        deepskyblue: '00bfff',
        dimgray: '696969',
        dodgerblue: '1e90ff',
        feldspar: 'd19275',
        firebrick: 'b22222',
        floralwhite: 'fffaf0',
        forestgreen: '228b22',
        fuchsia: 'ff00ff',
        gainsboro: 'dcdcdc',
        ghostwhite: 'f8f8ff',
        gold: 'ffd700',
        goldenrod: 'daa520',
        gray: '808080',
        green: '008000',
        greenyellow: 'adff2f',
        honeydew: 'f0fff0',
        hotpink: 'ff69b4',
        indianred : 'cd5c5c',
        indigo : '4b0082',
        ivory: 'fffff0',
        khaki: 'f0e68c',
        lavender: 'e6e6fa',
        lavenderblush: 'fff0f5',
        lawngreen: '7cfc00',
        lemonchiffon: 'fffacd',
        lightblue: 'add8e6',
        lightcoral: 'f08080',
        lightcyan: 'e0ffff',
        lightgoldenrodyellow: 'fafad2',
        lightgrey: 'd3d3d3',
        lightgreen: '90ee90',
        lightpink: 'ffb6c1',
        lightsalmon: 'ffa07a',
        lightseagreen: '20b2aa',
        lightskyblue: '87cefa',
        lightslateblue: '8470ff',
        lightslategray: '778899',
        lightsteelblue: 'b0c4de',
        lightyellow: 'ffffe0',
        lime: '00ff00',
        limegreen: '32cd32',
        linen: 'faf0e6',
        magenta: 'ff00ff',
        maroon: '800000',
        mediumaquamarine: '66cdaa',
        mediumblue: '0000cd',
        mediumorchid: 'ba55d3',
        mediumpurple: '9370d8',
        mediumseagreen: '3cb371',
        mediumslateblue: '7b68ee',
        mediumspringgreen: '00fa9a',
        mediumturquoise: '48d1cc',
        mediumvioletred: 'c71585',
        midnightblue: '191970',
        mintcream: 'f5fffa',
        mistyrose: 'ffe4e1',
        moccasin: 'ffe4b5',
        navajowhite: 'ffdead',
        navy: '000080',
        oldlace: 'fdf5e6',
        olive: '808000',
        olivedrab: '6b8e23',
        orange: 'ffa500',
        orangered: 'ff4500',
        orchid: 'da70d6',
        palegoldenrod: 'eee8aa',
        palegreen: '98fb98',
        paleturquoise: 'afeeee',
        palevioletred: 'd87093',
        papayawhip: 'ffefd5',
        peachpuff: 'ffdab9',
        peru: 'cd853f',
        pink: 'ffc0cb',
        plum: 'dda0dd',
        powderblue: 'b0e0e6',
        purple: '800080',
        red: 'ff0000',
        rosybrown: 'bc8f8f',
        royalblue: '4169e1',
        saddlebrown: '8b4513',
        salmon: 'fa8072',
        sandybrown: 'f4a460',
        seagreen: '2e8b57',
        seashell: 'fff5ee',
        sienna: 'a0522d',
        silver: 'c0c0c0',
        skyblue: '87ceeb',
        slateblue: '6a5acd',
        slategray: '708090',
        snow: 'fffafa',
        springgreen: '00ff7f',
        steelblue: '4682b4',
        tan: 'd2b48c',
        teal: '008080',
        thistle: 'd8bfd8',
        tomato: 'ff6347',
        turquoise: '40e0d0',
        violet: 'ee82ee',
        violetred: 'd02090',
        wheat: 'f5deb3',
        white: 'ffffff',
        whitesmoke: 'f5f5f5',
        yellow: 'ffff00',
        yellowgreen: '9acd32'
    };
    for (var key in simple_colors) {
        if (color_string == key) {
            color_string = simple_colors[key];
        }
    }
    // emd of simple type-in colors

    // array of color definition objects
    var color_defs = [
        {
            re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
            example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
            process: function (bits){
                return [
                    parseInt(bits[1]),
                    parseInt(bits[2]),
                    parseInt(bits[3])
                ];
            }
        },
        {
            re: /^(\w{2})(\w{2})(\w{2})$/,
            example: ['#00ff00', '336699'],
            process: function (bits){
                return [
                    parseInt(bits[1], 16),
                    parseInt(bits[2], 16),
                    parseInt(bits[3], 16)
                ];
            }
        },
        {
            re: /^(\w{1})(\w{1})(\w{1})$/,
            example: ['#fb0', 'f0f'],
            process: function (bits){
                return [
                    parseInt(bits[1] + bits[1], 16),
                    parseInt(bits[2] + bits[2], 16),
                    parseInt(bits[3] + bits[3], 16)
                ];
            }
        }
    ];

    // search through the definitions to find a match
    for (var i = 0; i < color_defs.length; i++) {
        var re = color_defs[i].re;
        var processor = color_defs[i].process;
        var bits = re.exec(color_string);
        if (bits) {
            channels = processor(bits);
            this.r = channels[0];
            this.g = channels[1];
            this.b = channels[2];
            this.ok = true;
        }

    }

    // validate/cleanup values
    this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
    this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
    this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);

    // some getters
    this.toRGB = function () {
        return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
    }
    this.toHex = function () {
        var r = this.r.toString(16);
        var g = this.g.toString(16);
        var b = this.b.toString(16);
        if (r.length == 1) r = '0' + r;
        if (g.length == 1) g = '0' + g;
        if (b.length == 1) b = '0' + b;
        return '#' + r + g + b;
    }

    // help
    this.getHelpXML = function () {

        var examples = new Array();
        // add regexps
        for (var i = 0; i < color_defs.length; i++) {
            var example = color_defs[i].example;
            for (var j = 0; j < example.length; j++) {
                examples[examples.length] = example[j];
            }
        }
        // add type-in colors
        for (var sc in simple_colors) {
            examples[examples.length] = sc;
        }

        var xml = document.createElement('ul');
        xml.setAttribute('id', 'rgbcolor-examples');
        for (var i = 0; i < examples.length; i++) {
            try {
                var list_item = document.createElement('li');
                var list_color = new RGBColor(examples[i]);
                var example_div = document.createElement('div');
                example_div.style.cssText =
                        'margin: 3px; '
                        + 'border: 1px solid black; '
                        + 'background:' + list_color.toHex() + '; '
                        + 'color:' + list_color.toHex()
                ;
                example_div.appendChild(document.createTextNode('test'));
                var list_item_value = document.createTextNode(
                    ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()
                );
                list_item.appendChild(example_div);
                list_item.appendChild(list_item_value);
                xml.appendChild(list_item);

            } catch(e){}
        }
        return xml;

    }

}

;
define("rgbcolor", (function (global) {
    return function () {
        var ret, fn;
        return ret || global.rgbcolor;
    };
}(this)));

/*
 * canvg.js - Javascript SVG parser and renderer on Canvas
 * MIT Licensed 
 * Gabe Lerner (gabelerner@gmail.com)
 * http://code.google.com/p/canvg/
 *
 * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
 */
(function(){
	// canvg(target, s)
	// empty parameters: replace all 'svg' elements on page with 'canvas' elements
	// target: canvas element or the id of a canvas element
	// s: svg string, url to svg file, or xml document
	// opts: optional hash of options
	//		 ignoreMouse: true => ignore mouse events
	//		 ignoreAnimation: true => ignore animations
	//		 ignoreDimensions: true => does not try to resize canvas
	//		 ignoreClear: true => does not clear canvas
	//		 offsetX: int => draws at a x offset
	//		 offsetY: int => draws at a y offset
	//		 scaleWidth: int => scales horizontally to width
	//		 scaleHeight: int => scales vertically to height
	//		 renderCallback: function => will call the function after the first render is completed
	//		 forceRedraw: function => will call the function on every frame, if it returns true, will redraw
	this.canvg = function (target, s, opts) {
		// no parameters
		if (target == null && s == null && opts == null) {
			var svgTags = document.getElementsByTagName('svg');
			for (var i=0; i<svgTags.length; i++) {
				var svgTag = svgTags[i];
				var c = document.createElement('canvas');
				c.width = svgTag.clientWidth;
				c.height = svgTag.clientHeight;
				svgTag.parentNode.insertBefore(c, svgTag);
				svgTag.parentNode.removeChild(svgTag);
				var div = document.createElement('div');
				div.appendChild(svgTag);
				canvg(c, div.innerHTML);
			}
			return;
		}	
		opts = opts || {};
	
		if (typeof target == 'string') {
			target = document.getElementById(target);
		}
		
		// store class on canvas
		if (target.svg != null) target.svg.stop();
		var svg = build();
		// on i.e. 8 for flash canvas, we can't assign the property so check for it
		if (!(target.childNodes.length == 1 && target.childNodes[0].nodeName == 'OBJECT')) target.svg = svg;
		svg.opts = opts;
		
		var ctx = target.getContext('2d');
		if (typeof(s.documentElement) != 'undefined') {
			// load from xml doc
			svg.loadXmlDoc(ctx, s);
		}
		else if (s.substr(0,1) == '<') {
			// load from xml string
			svg.loadXml(ctx, s);
		}
		else {
			// load from url
			svg.load(ctx, s);
		}
	}

	function build() {
		var svg = { };
		
		svg.FRAMERATE = 30;
		svg.MAX_VIRTUAL_PIXELS = 30000;
		
		// globals
		svg.init = function(ctx) {
			var uniqueId = 0;
			svg.UniqueId = function () { uniqueId++; return 'canvg' + uniqueId;	};
			svg.Definitions = {};
			svg.Styles = {};
			svg.Animations = [];
			svg.Images = [];
			svg.ctx = ctx;
			svg.ViewPort = new (function () {
				this.viewPorts = [];
				this.Clear = function() { this.viewPorts = []; }
				this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
				this.RemoveCurrent = function() { this.viewPorts.pop(); }
				this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
				this.width = function() { return this.Current().width; }
				this.height = function() { return this.Current().height; }
				this.ComputeSize = function(d) {
					if (d != null && typeof(d) == 'number') return d;
					if (d == 'x') return this.width();
					if (d == 'y') return this.height();
					return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);			
				}
			});
		}
		svg.init();
		
		// images loaded
		svg.ImagesLoaded = function() { 
			for (var i=0; i<svg.Images.length; i++) {
				if (!svg.Images[i].loaded) return false;
			}
			return true;
		}

		// trim
		svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
		
		// compress spaces
		svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
		
		// ajax
		svg.ajax = function(url) {
			var AJAX;
			if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
			else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
			if(AJAX){
			   AJAX.open('GET',url,false);
			   AJAX.send(null);
			   return AJAX.responseText;
			}
			return null;
		} 
		
		// parse xml
		svg.parseXml = function(xml) {
			if (window.DOMParser)
			{
				var parser = new DOMParser();
				return parser.parseFromString(xml, 'text/xml');
			}
			else 
			{
				xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
				var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
				xmlDoc.async = 'false';
				xmlDoc.loadXML(xml); 
				return xmlDoc;
			}		
		}
		
		svg.Property = function(name, value) {
			this.name = name;
			this.value = value;
		}	
			svg.Property.prototype.getValue = function() {
				return this.value;
			}
		
			svg.Property.prototype.hasValue = function() {
				return (this.value != null && this.value !== '');
			}
							
			// return the numerical value of the property
			svg.Property.prototype.numValue = function() {
				if (!this.hasValue()) return 0;
				
				var n = parseFloat(this.value);
				if ((this.value + '').match(/%$/)) {
					n = n / 100.0;
				}
				return n;
			}
			
			svg.Property.prototype.valueOrDefault = function(def) {
				if (this.hasValue()) return this.value;
				return def;
			}
			
			svg.Property.prototype.numValueOrDefault = function(def) {
				if (this.hasValue()) return this.numValue();
				return def;
			}
			
			// color extensions
				// augment the current color value with the opacity
				svg.Property.prototype.addOpacity = function(opacity) {
					var newValue = this.value;
					if (opacity != null && opacity != '' && typeof(this.value)=='string') { // can only add opacity to colors, not patterns
						var color = new RGBColor(this.value);
						if (color.ok) {
							newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';
						}
					}
					return new svg.Property(this.name, newValue);
				}
			
			// definition extensions
				// get the definition from the definitions table
				svg.Property.prototype.getDefinition = function() {
					var name = this.value.match(/#([^\)'"]+)/);
					if (name) { name = name[1]; }
					if (!name) { name = this.value; }
					return svg.Definitions[name];
				}
				
				svg.Property.prototype.isUrlDefinition = function() {
					return this.value.indexOf('url(') == 0
				}
				
				svg.Property.prototype.getFillStyleDefinition = function(e, opacityProp) {
					var def = this.getDefinition();
					
					// gradient
					if (def != null && def.createGradient) {
						return def.createGradient(svg.ctx, e, opacityProp);
					}
					
					// pattern
					if (def != null && def.createPattern) {
						if (def.getHrefAttribute().hasValue()) {
							var pt = def.attribute('patternTransform');
							def = def.getHrefAttribute().getDefinition();
							if (pt.hasValue()) { def.attribute('patternTransform', true).value = pt.value; }
						}
						return def.createPattern(svg.ctx, e);
					}
					
					return null;
				}
			
			// length extensions
				svg.Property.prototype.getDPI = function(viewPort) {
					return 96.0; // TODO: compute?
				}
				
				svg.Property.prototype.getEM = function(viewPort) {
					var em = 12;
					
					var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
					if (fontSize.hasValue()) em = fontSize.toPixels(viewPort);
					
					return em;
				}
				
				svg.Property.prototype.getUnits = function() {
					var s = this.value+'';
					return s.replace(/[0-9\.\-]/g,'');
				}
			
				// get the length as pixels
				svg.Property.prototype.toPixels = function(viewPort, processPercent) {
					if (!this.hasValue()) return 0;
					var s = this.value+'';
					if (s.match(/em$/)) return this.numValue() * this.getEM(viewPort);
					if (s.match(/ex$/)) return this.numValue() * this.getEM(viewPort) / 2.0;
					if (s.match(/px$/)) return this.numValue();
					if (s.match(/pt$/)) return this.numValue() * this.getDPI(viewPort) * (1.0 / 72.0);
					if (s.match(/pc$/)) return this.numValue() * 15;
					if (s.match(/cm$/)) return this.numValue() * this.getDPI(viewPort) / 2.54;
					if (s.match(/mm$/)) return this.numValue() * this.getDPI(viewPort) / 25.4;
					if (s.match(/in$/)) return this.numValue() * this.getDPI(viewPort);
					if (s.match(/%$/)) return this.numValue() * svg.ViewPort.ComputeSize(viewPort);
					var n = this.numValue();
					if (processPercent && n < 1.0) return n * svg.ViewPort.ComputeSize(viewPort);
					return n;
				}

			// time extensions
				// get the time as milliseconds
				svg.Property.prototype.toMilliseconds = function() {
					if (!this.hasValue()) return 0;
					var s = this.value+'';
					if (s.match(/s$/)) return this.numValue() * 1000;
					if (s.match(/ms$/)) return this.numValue();
					return this.numValue();
				}
			
			// angle extensions
				// get the angle as radians
				svg.Property.prototype.toRadians = function() {
					if (!this.hasValue()) return 0;
					var s = this.value+'';
					if (s.match(/deg$/)) return this.numValue() * (Math.PI / 180.0);
					if (s.match(/grad$/)) return this.numValue() * (Math.PI / 200.0);
					if (s.match(/rad$/)) return this.numValue();
					return this.numValue() * (Math.PI / 180.0);
				}
		
		// fonts
		svg.Font = new (function() {
			this.Styles = 'normal|italic|oblique|inherit';
			this.Variants = 'normal|small-caps|inherit';
			this.Weights = 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit';
			
			this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) { 
				var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
				return { 
					fontFamily: fontFamily || f.fontFamily, 
					fontSize: fontSize || f.fontSize, 
					fontStyle: fontStyle || f.fontStyle, 
					fontWeight: fontWeight || f.fontWeight, 
					fontVariant: fontVariant || f.fontVariant,
					toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') } 
				} 
			}
			
			var that = this;
			this.Parse = function(s) {
				var f = {};
				var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
				var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
				var ff = '';
				for (var i=0; i<d.length; i++) {
					if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
					else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true;	}
					else if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) {	if (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }
					else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
					else { if (d[i] != 'inherit') ff += d[i]; }
				} if (ff != '') f.fontFamily = ff;
				return f;
			}
		});
		
		// points and paths
		svg.ToNumberArray = function(s) {
			var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
			for (var i=0; i<a.length; i++) {
				a[i] = parseFloat(a[i]);
			}
			return a;
		}		
		svg.Point = function(x, y) {
			this.x = x;
			this.y = y;
		}	
			svg.Point.prototype.angleTo = function(p) {
				return Math.atan2(p.y - this.y, p.x - this.x);
			}
			
			svg.Point.prototype.applyTransform = function(v) {
				var xp = this.x * v[0] + this.y * v[2] + v[4];
				var yp = this.x * v[1] + this.y * v[3] + v[5];
				this.x = xp;
				this.y = yp;
			}

		svg.CreatePoint = function(s) {
			var a = svg.ToNumberArray(s);
			return new svg.Point(a[0], a[1]);
		}
		svg.CreatePath = function(s) {
			var a = svg.ToNumberArray(s);
			var path = [];
			for (var i=0; i<a.length; i+=2) {
				path.push(new svg.Point(a[i], a[i+1]));
			}
			return path;
		}
		
		// bounding box
		svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
			this.x1 = Number.NaN;
			this.y1 = Number.NaN;
			this.x2 = Number.NaN;
			this.y2 = Number.NaN;
			
			this.x = function() { return this.x1; }
			this.y = function() { return this.y1; }
			this.width = function() { return this.x2 - this.x1; }
			this.height = function() { return this.y2 - this.y1; }
			
			this.addPoint = function(x, y) {	
				if (x != null) {
					if (isNaN(this.x1) || isNaN(this.x2)) {
						this.x1 = x;
						this.x2 = x;
					}
					if (x < this.x1) this.x1 = x;
					if (x > this.x2) this.x2 = x;
				}
			
				if (y != null) {
					if (isNaN(this.y1) || isNaN(this.y2)) {
						this.y1 = y;
						this.y2 = y;
					}
					if (y < this.y1) this.y1 = y;
					if (y > this.y2) this.y2 = y;
				}
			}			
			this.addX = function(x) { this.addPoint(x, null); }
			this.addY = function(y) { this.addPoint(null, y); }
			
			this.addBoundingBox = function(bb) {
				this.addPoint(bb.x1, bb.y1);
				this.addPoint(bb.x2, bb.y2);
			}
			
			this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
				var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
				var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
				var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
				var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
				this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y,	cp2y, p2x, p2y);
			}
			
			this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
				// from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
				var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
				this.addPoint(p0[0], p0[1]);
				this.addPoint(p3[0], p3[1]);
				
				for (i=0; i<=1; i++) {
					var f = function(t) { 
						return Math.pow(1-t, 3) * p0[i]
						+ 3 * Math.pow(1-t, 2) * t * p1[i]
						+ 3 * (1-t) * Math.pow(t, 2) * p2[i]
						+ Math.pow(t, 3) * p3[i];
					}
					
					var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
					var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
					var c = 3 * p1[i] - 3 * p0[i];
					
					if (a == 0) {
						if (b == 0) continue;
						var t = -c / b;
						if (0 < t && t < 1) {
							if (i == 0) this.addX(f(t));
							if (i == 1) this.addY(f(t));
						}
						continue;
					}
					
					var b2ac = Math.pow(b, 2) - 4 * c * a;
					if (b2ac < 0) continue;
					var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
					if (0 < t1 && t1 < 1) {
						if (i == 0) this.addX(f(t1));
						if (i == 1) this.addY(f(t1));
					}
					var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
					if (0 < t2 && t2 < 1) {
						if (i == 0) this.addX(f(t2));
						if (i == 1) this.addY(f(t2));
					}
				}
			}
			
			this.isPointInBox = function(x, y) {
				return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
			}
			
			this.addPoint(x1, y1);
			this.addPoint(x2, y2);
		}
		
		// transforms
		svg.Transform = function(v) {	
			var that = this;
			this.Type = {}
		
			// translate
			this.Type.translate = function(s) {
				this.p = svg.CreatePoint(s);			
				this.apply = function(ctx) {
					ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
				}
				this.unapply = function(ctx) {
					ctx.translate(-1.0 * this.p.x || 0.0, -1.0 * this.p.y || 0.0);
				}
				this.applyToPoint = function(p) {
					p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
				}
			}
			
			// rotate
			this.Type.rotate = function(s) {
				var a = svg.ToNumberArray(s);
				this.angle = new svg.Property('angle', a[0]);
				this.cx = a[1] || 0;
				this.cy = a[2] || 0;
				this.apply = function(ctx) {
					ctx.translate(this.cx, this.cy);
					ctx.rotate(this.angle.toRadians());
					ctx.translate(-this.cx, -this.cy);
				}
				this.unapply = function(ctx) {
					ctx.translate(this.cx, this.cy);
					ctx.rotate(-1.0 * this.angle.toRadians());
					ctx.translate(-this.cx, -this.cy);
				}
				this.applyToPoint = function(p) {
					var a = this.angle.toRadians();
					p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
					p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
					p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
				}			
			}
			
			this.Type.scale = function(s) {
				this.p = svg.CreatePoint(s);
				this.apply = function(ctx) {
					ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
				}
				this.unapply = function(ctx) {
					ctx.scale(1.0 / this.p.x || 1.0, 1.0 / this.p.y || this.p.x || 1.0);
				}
				this.applyToPoint = function(p) {
					p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
				}				
			}
			
			this.Type.matrix = function(s) {
				this.m = svg.ToNumberArray(s);
				this.apply = function(ctx) {
					ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
				}
				this.applyToPoint = function(p) {
					p.applyTransform(this.m);
				}					
			}
			
			this.Type.SkewBase = function(s) {
				this.base = that.Type.matrix;
				this.base(s);
				this.angle = new svg.Property('angle', s);
			}
			this.Type.SkewBase.prototype = new this.Type.matrix;
			
			this.Type.skewX = function(s) {
				this.base = that.Type.SkewBase;
				this.base(s);
				this.m = [1, 0, Math.tan(this.angle.toRadians()), 1, 0, 0];
			}
			this.Type.skewX.prototype = new this.Type.SkewBase;
			
			this.Type.skewY = function(s) {
				this.base = that.Type.SkewBase;
				this.base(s);
				this.m = [1, Math.tan(this.angle.toRadians()), 0, 1, 0, 0];
			}
			this.Type.skewY.prototype = new this.Type.SkewBase;
		
			this.transforms = [];
			
			this.apply = function(ctx) {
				for (var i=0; i<this.transforms.length; i++) {
					this.transforms[i].apply(ctx);
				}
			}
			
			this.unapply = function(ctx) {
				for (var i=this.transforms.length-1; i>=0; i--) {
					this.transforms[i].unapply(ctx);
				}
			}
			
			this.applyToPoint = function(p) {
				for (var i=0; i<this.transforms.length; i++) {
					this.transforms[i].applyToPoint(p);
				}
			}
			
			var data = svg.trim(svg.compressSpaces(v)).replace(/\)(\s?,\s?)/g,') ').split(/\s(?=[a-z])/);
			for (var i=0; i<data.length; i++) {
				var type = svg.trim(data[i].split('(')[0]);
				var s = data[i].split('(')[1].replace(')','');
				var transform = new this.Type[type](s);
				transform.type = type;
				this.transforms.push(transform);
			}
		}
		
		// aspect ratio
		svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
			// aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
			aspectRatio = svg.compressSpaces(aspectRatio);
			aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
			var align = aspectRatio.split(' ')[0] || 'xMidYMid';
			var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';					
	
			// calculate scale
			var scaleX = width / desiredWidth;
			var scaleY = height / desiredHeight;
			var scaleMin = Math.min(scaleX, scaleY);
			var scaleMax = Math.max(scaleX, scaleY);
			if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
			if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }	
			
			refX = new svg.Property('refX', refX);
			refY = new svg.Property('refY', refY);
			if (refX.hasValue() && refY.hasValue()) {				
				ctx.translate(-scaleMin * refX.toPixels('x'), -scaleMin * refY.toPixels('y'));
			} 
			else {					
				// align
				if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0); 
				if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0); 
				if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0); 
				if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight); 
			}
			
			// scale
			if (align == 'none') ctx.scale(scaleX, scaleY);
			else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin); 
			else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax); 	
			
			// translate
			ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);			
		}
		
		// elements
		svg.Element = {}
		
		svg.EmptyProperty = new svg.Property('EMPTY', '');
		
		svg.Element.ElementBase = function(node) {	
			this.attributes = {};
			this.styles = {};
			this.children = [];
			
			// get or create attribute
			this.attribute = function(name, createIfNotExists) {
				var a = this.attributes[name];
				if (a != null) return a;
							
				if (createIfNotExists == true) { a = new svg.Property(name, ''); this.attributes[name] = a; }
				return a || svg.EmptyProperty;
			}
			
			this.getHrefAttribute = function() {
				for (var a in this.attributes) { 
					if (a.match(/:href$/)) { 
						return this.attributes[a]; 
					} 
				}
				return svg.EmptyProperty;
			}
			
			// get or create style, crawls up node tree
			this.style = function(name, createIfNotExists) {
				var s = this.styles[name];
				if (s != null) return s;
				
				var a = this.attribute(name);
				if (a != null && a.hasValue()) {
					this.styles[name] = a; // move up to me to cache
					return a;
				}
				
				var p = this.parent;
				if (p != null) {
					var ps = p.style(name);
					if (ps != null && ps.hasValue()) {
						return ps;
					}
				}
					
				if (createIfNotExists == true) { s = new svg.Property(name, ''); this.styles[name] = s; }
				return s || svg.EmptyProperty;
			}
			
			// base render
			this.render = function(ctx) {
				// don't render display=none
				if (this.style('display').value == 'none') return;
				
				// don't render visibility=hidden
				if (this.attribute('visibility').value == 'hidden') return;
			
				ctx.save();
				if (this.attribute('mask').hasValue()) { // mask
					var mask = this.attribute('mask').getDefinition();
					if (mask != null) mask.apply(ctx, this);
				}
				else if (this.style('filter').hasValue()) { // filter
					var filter = this.style('filter').getDefinition();
					if (filter != null) filter.apply(ctx, this);
				}
				else {	
					this.setContext(ctx);
					this.renderChildren(ctx);	
					this.clearContext(ctx);							
				}
				ctx.restore();
			}
			
			// base set context
			this.setContext = function(ctx) {
				// OVERRIDE ME!
			}
			
			// base clear context
			this.clearContext = function(ctx) {
				// OVERRIDE ME!
			}			
			
			// base render children
			this.renderChildren = function(ctx) {
				for (var i=0; i<this.children.length; i++) {
					this.children[i].render(ctx);
				}
			}
			
			this.addChild = function(childNode, create) {
				var child = childNode;
				if (create) child = svg.CreateElement(childNode);
				child.parent = this;
				this.children.push(child);			
			}
				
			if (node != null && node.nodeType == 1) { //ELEMENT_NODE
				// add children
				for (var i=0; i<node.childNodes.length; i++) {
					var childNode = node.childNodes[i];
					if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
					if (this.captureTextNodes && childNode.nodeType == 3) {
						var text = childNode.nodeValue || childNode.text || '';
						if (svg.trim(svg.compressSpaces(text)) != '') {
							this.addChild(new svg.Element.tspan(childNode), false); // TEXT_NODE
						}
					}
				}
				
				// add attributes
				for (var i=0; i<node.attributes.length; i++) {
					var attribute = node.attributes[i];
					this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
				}
										
				// add tag styles
				var styles = svg.Styles[node.nodeName];
				if (styles != null) {
					for (var name in styles) {
						this.styles[name] = styles[name];
					}
				}					
				
				// add class styles
				if (this.attribute('class').hasValue()) {
					var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
					for (var j=0; j<classes.length; j++) {
						styles = svg.Styles['.'+classes[j]];
						if (styles != null) {
							for (var name in styles) {
								this.styles[name] = styles[name];
							}
						}
						styles = svg.Styles[node.nodeName+'.'+classes[j]];
						if (styles != null) {
							for (var name in styles) {
								this.styles[name] = styles[name];
							}
						}
					}
				}
				
				// add id styles
				if (this.attribute('id').hasValue()) {
					var styles = svg.Styles['#' + this.attribute('id').value];
					if (styles != null) {
						for (var name in styles) {
							this.styles[name] = styles[name];
						}
					}
				}
				
				// add inline styles
				if (this.attribute('style').hasValue()) {
					var styles = this.attribute('style').value.split(';');
					for (var i=0; i<styles.length; i++) {
						if (svg.trim(styles[i]) != '') {
							var style = styles[i].split(':');
							var name = svg.trim(style[0]);
							var value = svg.trim(style[1]);
							this.styles[name] = new svg.Property(name, value);
						}
					}
				}	

				// add id
				if (this.attribute('id').hasValue()) {
					if (svg.Definitions[this.attribute('id').value] == null) {
						svg.Definitions[this.attribute('id').value] = this;
					}
				}
			}
		}
		
		svg.Element.RenderedElementBase = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			this.setContext = function(ctx) {
				// fill
				if (this.style('fill').isUrlDefinition()) {
					var fs = this.style('fill').getFillStyleDefinition(this, this.style('fill-opacity'));
					if (fs != null) ctx.fillStyle = fs;
				}
				else if (this.style('fill').hasValue()) {
					var fillStyle = this.style('fill');
					if (fillStyle.value == 'currentColor') fillStyle.value = this.style('color').value;
					ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
				}
				if (this.style('fill-opacity').hasValue()) {
					var fillStyle = new svg.Property('fill', ctx.fillStyle);
					fillStyle = fillStyle.addOpacity(this.style('fill-opacity').value);
					ctx.fillStyle = fillStyle.value;
				}
									
				// stroke
				if (this.style('stroke').isUrlDefinition()) {
					var fs = this.style('stroke').getFillStyleDefinition(this, this.style('stroke-opacity'));
					if (fs != null) ctx.strokeStyle = fs;
				}
				else if (this.style('stroke').hasValue()) {
					var strokeStyle = this.style('stroke');
					if (strokeStyle.value == 'currentColor') strokeStyle.value = this.style('color').value;
					ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
				}
				if (this.style('stroke-opacity').hasValue()) {
					var strokeStyle = new svg.Property('stroke', ctx.strokeStyle);
					strokeStyle = strokeStyle.addOpacity(this.style('stroke-opacity').value);
					ctx.strokeStyle = strokeStyle.value;
				}
				if (this.style('stroke-width').hasValue()) {
					var newLineWidth = this.style('stroke-width').toPixels();
					ctx.lineWidth = newLineWidth == 0 ? 0.001 : newLineWidth; // browsers don't respect 0
			    }
				if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
				if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
				if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
				if (this.style('stroke-dasharray').hasValue()) {
					var gaps = svg.ToNumberArray(this.style('stroke-dasharray').value);
					if (typeof(ctx.setLineDash) != 'undefined') { ctx.setLineDash(gaps); }
					else if (typeof(ctx.webkitLineDash) != 'undefined') { ctx.webkitLineDash = gaps; }
					else if (typeof(ctx.mozDash ) != 'undefined') { ctx.mozDash  = gaps; }
					
					var offset = this.style('stroke-dashoffset').numValueOrDefault(1);
					if (typeof(ctx.lineDashOffset) != 'undefined') { ctx.lineDashOffset = offset; }
					else if (typeof(ctx.webkitLineDashOffset) != 'undefined') { ctx.webkitLineDashOffset = offset; }
					else if (typeof(ctx.mozDashOffset) != 'undefined') { ctx.mozDashOffset = offset; }
				}

				// font
				if (typeof(ctx.font) != 'undefined') {
					ctx.font = svg.Font.CreateFont( 
						this.style('font-style').value, 
						this.style('font-variant').value, 
						this.style('font-weight').value, 
						this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '', 
						this.style('font-family').value).toString();
				}
				
				// transform
				if (this.attribute('transform').hasValue()) { 
					var transform = new svg.Transform(this.attribute('transform').value);
					transform.apply(ctx);
				}
				
				// clip
				if (this.style('clip-path').hasValue()) {
					var clip = this.style('clip-path').getDefinition();
					if (clip != null) clip.apply(ctx);
				}
				
				// opacity
				if (this.style('opacity').hasValue()) {
					ctx.globalAlpha = this.style('opacity').numValue();
				}
			}		
		}
		svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
		
		svg.Element.PathElementBase = function(node) {
			this.base = svg.Element.RenderedElementBase;
			this.base(node);
			
			this.path = function(ctx) {
				if (ctx != null) ctx.beginPath();
				return new svg.BoundingBox();
			}
			
			this.renderChildren = function(ctx) {
				this.path(ctx);
				svg.Mouse.checkPath(this, ctx);
				if (ctx.fillStyle != '') {
					if (this.attribute('fill-rule').hasValue()) { ctx.fill(this.attribute('fill-rule').value); }
					else { ctx.fill(); }
				}
				if (ctx.strokeStyle != '') ctx.stroke();
				
				var markers = this.getMarkers();
				if (markers != null) {
					if (this.style('marker-start').isUrlDefinition()) {
						var marker = this.style('marker-start').getDefinition();
						marker.render(ctx, markers[0][0], markers[0][1]);
					}
					if (this.style('marker-mid').isUrlDefinition()) {
						var marker = this.style('marker-mid').getDefinition();
						for (var i=1;i<markers.length-1;i++) {
							marker.render(ctx, markers[i][0], markers[i][1]);
						}
					}
					if (this.style('marker-end').isUrlDefinition()) {
						var marker = this.style('marker-end').getDefinition();
						marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
					}
				}					
			}
			
			this.getBoundingBox = function() {
				return this.path();
			}
			
			this.getMarkers = function() {
				return null;
			}
		}
		svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
		
		// svg element
		svg.Element.svg = function(node) {
			this.base = svg.Element.RenderedElementBase;
			this.base(node);
			
			this.baseClearContext = this.clearContext;
			this.clearContext = function(ctx) {
				this.baseClearContext(ctx);
				svg.ViewPort.RemoveCurrent();
			}
			
			this.baseSetContext = this.setContext;
			this.setContext = function(ctx) {
				// initial values
				ctx.strokeStyle = 'rgba(0,0,0,0)';
				ctx.lineCap = 'butt';
				ctx.lineJoin = 'miter';
				ctx.miterLimit = 4;			
			
				this.baseSetContext(ctx);
				
				// create new view port
				if (!this.attribute('x').hasValue()) this.attribute('x', true).value = 0;
				if (!this.attribute('y').hasValue()) this.attribute('y', true).value = 0;
				ctx.translate(this.attribute('x').toPixels('x'), this.attribute('y').toPixels('y'));
				
				var width = svg.ViewPort.width();
				var height = svg.ViewPort.height();
				
				if (!this.attribute('width').hasValue()) this.attribute('width', true).value = '100%';
				if (!this.attribute('height').hasValue()) this.attribute('height', true).value = '100%';
				if (typeof(this.root) == 'undefined') {
					width = this.attribute('width').toPixels('x');
					height = this.attribute('height').toPixels('y');
					
					var x = 0;
					var y = 0;
					if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
						x = -this.attribute('refX').toPixels('x');
						y = -this.attribute('refY').toPixels('y');
					}
					
					ctx.beginPath();
					ctx.moveTo(x, y);
					ctx.lineTo(width, y);
					ctx.lineTo(width, height);
					ctx.lineTo(x, height);
					ctx.closePath();
					ctx.clip();
				}
				svg.ViewPort.SetCurrent(width, height);	
						
				// viewbox
				if (this.attribute('viewBox').hasValue()) {				
					var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
					var minX = viewBox[0];
					var minY = viewBox[1];
					width = viewBox[2];
					height = viewBox[3];
					
					svg.AspectRatio(ctx,
									this.attribute('preserveAspectRatio').value, 
									svg.ViewPort.width(), 
									width,
									svg.ViewPort.height(),
									height,
									minX,
									minY,
									this.attribute('refX').value,
									this.attribute('refY').value);
										
					svg.ViewPort.RemoveCurrent();	
					svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);						
				}				
			}
		}
		svg.Element.svg.prototype = new svg.Element.RenderedElementBase;

		// rect element
		svg.Element.rect = function(node) {
			this.base = svg.Element.PathElementBase;
			this.base(node);
			
			this.path = function(ctx) {
				var x = this.attribute('x').toPixels('x');
				var y = this.attribute('y').toPixels('y');
				var width = this.attribute('width').toPixels('x');
				var height = this.attribute('height').toPixels('y');
				var rx = this.attribute('rx').toPixels('x');
				var ry = this.attribute('ry').toPixels('y');
				if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
				if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
				rx = Math.min(rx, width / 2.0);
				ry = Math.min(ry, height / 2.0);
				if (ctx != null) {
					ctx.beginPath();
					ctx.moveTo(x + rx, y);
					ctx.lineTo(x + width - rx, y);
					ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
					ctx.lineTo(x + width, y + height - ry);
					ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
					ctx.lineTo(x + rx, y + height);
					ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
					ctx.lineTo(x, y + ry);
					ctx.quadraticCurveTo(x, y, x + rx, y)
					ctx.closePath();
				}
				
				return new svg.BoundingBox(x, y, x + width, y + height);
			}
		}
		svg.Element.rect.prototype = new svg.Element.PathElementBase;
		
		// circle element
		svg.Element.circle = function(node) {
			this.base = svg.Element.PathElementBase;
			this.base(node);
			
			this.path = function(ctx) {
				var cx = this.attribute('cx').toPixels('x');
				var cy = this.attribute('cy').toPixels('y');
				var r = this.attribute('r').toPixels();
			
				if (ctx != null) {
					ctx.beginPath();
					ctx.arc(cx, cy, r, 0, Math.PI * 2, true); 
					ctx.closePath();
				}
				
				return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
			}
		}
		svg.Element.circle.prototype = new svg.Element.PathElementBase;	

		// ellipse element
		svg.Element.ellipse = function(node) {
			this.base = svg.Element.PathElementBase;
			this.base(node);
			
			this.path = function(ctx) {
				var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
				var rx = this.attribute('rx').toPixels('x');
				var ry = this.attribute('ry').toPixels('y');
				var cx = this.attribute('cx').toPixels('x');
				var cy = this.attribute('cy').toPixels('y');
				
				if (ctx != null) {
					ctx.beginPath();
					ctx.moveTo(cx, cy - ry);
					ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry,  cx + rx, cy - (KAPPA * ry), cx + rx, cy);
					ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
					ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
					ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
					ctx.closePath();
				}
				
				return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
			}
		}
		svg.Element.ellipse.prototype = new svg.Element.PathElementBase;			
		
		// line element
		svg.Element.line = function(node) {
			this.base = svg.Element.PathElementBase;
			this.base(node);
			
			this.getPoints = function() {
				return [
					new svg.Point(this.attribute('x1').toPixels('x'), this.attribute('y1').toPixels('y')),
					new svg.Point(this.attribute('x2').toPixels('x'), this.attribute('y2').toPixels('y'))];
			}
								
			this.path = function(ctx) {
				var points = this.getPoints();
				
				if (ctx != null) {
					ctx.beginPath();
					ctx.moveTo(points[0].x, points[0].y);
					ctx.lineTo(points[1].x, points[1].y);
				}
				
				return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
			}
			
			this.getMarkers = function() {
				var points = this.getPoints();	
				var a = points[0].angleTo(points[1]);
				return [[points[0], a], [points[1], a]];
			}
		}
		svg.Element.line.prototype = new svg.Element.PathElementBase;		
				
		// polyline element
		svg.Element.polyline = function(node) {
			this.base = svg.Element.PathElementBase;
			this.base(node);
			
			this.points = svg.CreatePath(this.attribute('points').value);
			this.path = function(ctx) {
				var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
				if (ctx != null) {
					ctx.beginPath();
					ctx.moveTo(this.points[0].x, this.points[0].y);
				}
				for (var i=1; i<this.points.length; i++) {
					bb.addPoint(this.points[i].x, this.points[i].y);
					if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
				}
				return bb;
			}
			
			this.getMarkers = function() {
				var markers = [];
				for (var i=0; i<this.points.length - 1; i++) {
					markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
				}
				markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
				return markers;
			}			
		}
		svg.Element.polyline.prototype = new svg.Element.PathElementBase;				
				
		// polygon element
		svg.Element.polygon = function(node) {
			this.base = svg.Element.polyline;
			this.base(node);
			
			this.basePath = this.path;
			this.path = function(ctx) {
				var bb = this.basePath(ctx);
				if (ctx != null) {
					ctx.lineTo(this.points[0].x, this.points[0].y);
					ctx.closePath();
				}
				return bb;
			}
		}
		svg.Element.polygon.prototype = new svg.Element.polyline;

		// path element
		svg.Element.path = function(node) {
			this.base = svg.Element.PathElementBase;
			this.base(node);
					
			var d = this.attribute('d').value;
			// TODO: convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
			d = d.replace(/,/gm,' '); // get rid of all commas
			d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
			d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
			d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
			d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
			d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
			d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
			d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
			d = svg.compressSpaces(d); // compress multiple spaces
			d = svg.trim(d);
			this.PathParser = new (function(d) {
				this.tokens = d.split(' ');
				
				this.reset = function() {
					this.i = -1;
					this.command = '';
					this.previousCommand = '';
					this.start = new svg.Point(0, 0);
					this.control = new svg.Point(0, 0);
					this.current = new svg.Point(0, 0);
					this.points = [];
					this.angles = [];
				}
								
				this.isEnd = function() {
					return this.i >= this.tokens.length - 1;
				}
				
				this.isCommandOrEnd = function() {
					if (this.isEnd()) return true;
					return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null;
				}
				
				this.isRelativeCommand = function() {
					switch(this.command)
					{
						case 'm':
						case 'l':
						case 'h':
						case 'v':
						case 'c':
						case 's':
						case 'q':
						case 't':
						case 'a':
						case 'z':
							return true;
							break;
					}
					return false;
				}
							
				this.getToken = function() {
					this.i++;
					return this.tokens[this.i];
				}
				
				this.getScalar = function() {
					return parseFloat(this.getToken());
				}
				
				this.nextCommand = function() {
					this.previousCommand = this.command;
					this.command = this.getToken();
				}				
				
				this.getPoint = function() {
					var p = new svg.Point(this.getScalar(), this.getScalar());
					return this.makeAbsolute(p);
				}
				
				this.getAsControlPoint = function() {
					var p = this.getPoint();
					this.control = p;
					return p;
				}
				
				this.getAsCurrentPoint = function() {
					var p = this.getPoint();
					this.current = p;
					return p;	
				}
				
				this.getReflectedControlPoint = function() {
					if (this.previousCommand.toLowerCase() != 'c' && 
					    this.previousCommand.toLowerCase() != 's' &&
						this.previousCommand.toLowerCase() != 'q' && 
						this.previousCommand.toLowerCase() != 't' ){
						return this.current;
					}
					
					// reflect point
					var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);					
					return p;
				}
				
				this.makeAbsolute = function(p) {
					if (this.isRelativeCommand()) {
						p.x += this.current.x;
						p.y += this.current.y;
					}
					return p;
				}
				
				this.addMarker = function(p, from, priorTo) {
					// if the last angle isn't filled in because we didn't have this point yet ...
					if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) {
						this.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo);
					}
					this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
				}
				
				this.addMarkerAngle = function(p, a) {
					this.points.push(p);
					this.angles.push(a);
				}				
				
				this.getMarkerPoints = function() { return this.points; }
				this.getMarkerAngles = function() {
					for (var i=0; i<this.angles.length; i++) {
						if (this.angles[i] == null) {
							for (var j=i+1; j<this.angles.length; j++) {
								if (this.angles[j] != null) {
									this.angles[i] = this.angles[j];
									break;
								}
							}
						}
					}
					return this.angles;
				}
			})(d);

			this.path = function(ctx) {
				var pp = this.PathParser;
				pp.reset();

				var bb = new svg.BoundingBox();
				if (ctx != null) ctx.beginPath();
				while (!pp.isEnd()) {
					pp.nextCommand();
					switch (pp.command) {
					case 'M':
					case 'm':
						var p = pp.getAsCurrentPoint();
						pp.addMarker(p);
						bb.addPoint(p.x, p.y);
						if (ctx != null) ctx.moveTo(p.x, p.y);
						pp.start = pp.current;
						while (!pp.isCommandOrEnd()) {
							var p = pp.getAsCurrentPoint();
							pp.addMarker(p, pp.start);
							bb.addPoint(p.x, p.y);
							if (ctx != null) ctx.lineTo(p.x, p.y);
						}
						break;
					case 'L':
					case 'l':
						while (!pp.isCommandOrEnd()) {
							var c = pp.current;
							var p = pp.getAsCurrentPoint();
							pp.addMarker(p, c);
							bb.addPoint(p.x, p.y);
							if (ctx != null) ctx.lineTo(p.x, p.y);
						}
						break;
					case 'H':
					case 'h':
						while (!pp.isCommandOrEnd()) {
							var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
							pp.addMarker(newP, pp.current);
							pp.current = newP;
							bb.addPoint(pp.current.x, pp.current.y);
							if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
						}
						break;
					case 'V':
					case 'v':
						while (!pp.isCommandOrEnd()) {
							var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
							pp.addMarker(newP, pp.current);
							pp.current = newP;
							bb.addPoint(pp.current.x, pp.current.y);
							if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
						}
						break;
					case 'C':
					case 'c':
						while (!pp.isCommandOrEnd()) {
							var curr = pp.current;
							var p1 = pp.getPoint();
							var cntrl = pp.getAsControlPoint();
							var cp = pp.getAsCurrentPoint();
							pp.addMarker(cp, cntrl, p1);
							bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
							if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
						}
						break;
					case 'S':
					case 's':
						while (!pp.isCommandOrEnd()) {
							var curr = pp.current;
							var p1 = pp.getReflectedControlPoint();
							var cntrl = pp.getAsControlPoint();
							var cp = pp.getAsCurrentPoint();
							pp.addMarker(cp, cntrl, p1);
							bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
							if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
						}
						break;
					case 'Q':
					case 'q':
						while (!pp.isCommandOrEnd()) {
							var curr = pp.current;
							var cntrl = pp.getAsControlPoint();
							var cp = pp.getAsCurrentPoint();
							pp.addMarker(cp, cntrl, cntrl);
							bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
							if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
						}
						break;
					case 'T':
					case 't':
						while (!pp.isCommandOrEnd()) {
							var curr = pp.current;
							var cntrl = pp.getReflectedControlPoint();
							pp.control = cntrl;
							var cp = pp.getAsCurrentPoint();
							pp.addMarker(cp, cntrl, cntrl);
							bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
							if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
						}
						break;
					case 'A':
					case 'a':
						while (!pp.isCommandOrEnd()) {
						    var curr = pp.current;
							var rx = pp.getScalar();
							var ry = pp.getScalar();
							var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
							var largeArcFlag = pp.getScalar();
							var sweepFlag = pp.getScalar();
							var cp = pp.getAsCurrentPoint();

							// Conversion from endpoint to center parameterization
							// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
							// x1', y1'
							var currp = new svg.Point(
								Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
								-Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
							);
							// adjust radii
							var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
							if (l > 1) {
								rx *= Math.sqrt(l);
								ry *= Math.sqrt(l);
							}
							// cx', cy'
							var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
								((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /
								(Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
							);
							if (isNaN(s)) s = 0;
							var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
							// cx, cy
							var centp = new svg.Point(
								(curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
								(curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
							);
							// vector magnitude
							var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
							// ratio between two vectors
							var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
							// angle between two vectors
							var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
							// initial angle
							var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
							// angle delta
							var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
							var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
							var ad = a(u, v);
							if (r(u,v) <= -1) ad = Math.PI;
							if (r(u,v) >= 1) ad = 0;

							// for markers
							var dir = 1 - sweepFlag ? 1.0 : -1.0;
							var ah = a1 + dir * (ad / 2.0);
							var halfWay = new svg.Point(
								centp.x + rx * Math.cos(ah),
								centp.y + ry * Math.sin(ah)
							);
							pp.addMarkerAngle(halfWay, ah - dir * Math.PI / 2);
							pp.addMarkerAngle(cp, ah - dir * Math.PI);

							bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
							if (ctx != null) {
								var r = rx > ry ? rx : ry;
								var sx = rx > ry ? 1 : rx / ry;
								var sy = rx > ry ? ry / rx : 1;

								ctx.translate(centp.x, centp.y);
								ctx.rotate(xAxisRotation);
								ctx.scale(sx, sy);
								ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
								ctx.scale(1/sx, 1/sy);
								ctx.rotate(-xAxisRotation);
								ctx.translate(-centp.x, -centp.y);
							}
						}
						break;
					case 'Z':
					case 'z':
						if (ctx != null) ctx.closePath();
						pp.current = pp.start;
					}
				}

				return bb;
			}

			this.getMarkers = function() {
				var points = this.PathParser.getMarkerPoints();
				var angles = this.PathParser.getMarkerAngles();
				
				var markers = [];
				for (var i=0; i<points.length; i++) {
					markers.push([points[i], angles[i]]);
				}
				return markers;
			}
		}
		svg.Element.path.prototype = new svg.Element.PathElementBase;
		
		// pattern element
		svg.Element.pattern = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			this.createPattern = function(ctx, element) {
				var width = this.attribute('width').toPixels('x', true);
				var height = this.attribute('height').toPixels('y', true);
			
				// render me using a temporary svg element
				var tempSvg = new svg.Element.svg();
				tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
				tempSvg.attributes['width'] = new svg.Property('width', width + 'px');
				tempSvg.attributes['height'] = new svg.Property('height', height + 'px');
				tempSvg.attributes['transform'] = new svg.Property('transform', this.attribute('patternTransform').value);
				tempSvg.children = this.children;
				
				var c = document.createElement('canvas');
				c.width = width;
				c.height = height;
				var cctx = c.getContext('2d');
				if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
					cctx.translate(this.attribute('x').toPixels('x', true), this.attribute('y').toPixels('y', true));
				}
				// render 3x3 grid so when we transform there's no white space on edges
				for (var x=-1; x<=1; x++) {
					for (var y=-1; y<=1; y++) {
						cctx.save();
						cctx.translate(x * c.width, y * c.height);
						tempSvg.render(cctx);
						cctx.restore();
					}
				}
				var pattern = ctx.createPattern(c, 'repeat');
				return pattern;
			}
		}
		svg.Element.pattern.prototype = new svg.Element.ElementBase;
		
		// marker element
		svg.Element.marker = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			this.baseRender = this.render;
			this.render = function(ctx, point, angle) {
				ctx.translate(point.x, point.y);
				if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
				if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
				ctx.save();
							
				// render me using a temporary svg element
				var tempSvg = new svg.Element.svg();
				tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
				tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
				tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
				tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
				tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
				tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
				tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
				tempSvg.children = this.children;
				tempSvg.render(ctx);
				
				ctx.restore();
				if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
				if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
				ctx.translate(-point.x, -point.y);
			}
		}
		svg.Element.marker.prototype = new svg.Element.ElementBase;
		
		// definitions element
		svg.Element.defs = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);	
			
			this.render = function(ctx) {
				// NOOP
			}
		}
		svg.Element.defs.prototype = new svg.Element.ElementBase;
		
		// base for gradients
		svg.Element.GradientBase = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
			
			this.stops = [];			
			for (var i=0; i<this.children.length; i++) {
				var child = this.children[i];
				if (child.type == 'stop') this.stops.push(child);
			}	
			
			this.getGradient = function() {
				// OVERRIDE ME!
			}			

			this.createGradient = function(ctx, element, parentOpacityProp) {
				var stopsContainer = this;
				if (this.getHrefAttribute().hasValue()) {
					stopsContainer = this.getHrefAttribute().getDefinition();
				}
				
				var addParentOpacity = function (color) {
					if (parentOpacityProp.hasValue()) {
						var p = new svg.Property('color', color);
						return p.addOpacity(parentOpacityProp.value).value;
					}
					return color;
				};
			
				var g = this.getGradient(ctx, element);
				if (g == null) return addParentOpacity(stopsContainer.stops[stopsContainer.stops.length - 1].color);
				for (var i=0; i<stopsContainer.stops.length; i++) {
					g.addColorStop(stopsContainer.stops[i].offset, addParentOpacity(stopsContainer.stops[i].color));
				}
				
				if (this.attribute('gradientTransform').hasValue()) {
					// render as transformed pattern on temporary canvas
					var rootView = svg.ViewPort.viewPorts[0];
					
					var rect = new svg.Element.rect();
					rect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);
					rect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);
					rect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
					rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
					
					var group = new svg.Element.g();
					group.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);
					group.children = [ rect ];
					
					var tempSvg = new svg.Element.svg();
					tempSvg.attributes['x'] = new svg.Property('x', 0);
					tempSvg.attributes['y'] = new svg.Property('y', 0);
					tempSvg.attributes['width'] = new svg.Property('width', rootView.width);
					tempSvg.attributes['height'] = new svg.Property('height', rootView.height);
					tempSvg.children = [ group ];
					
					var c = document.createElement('canvas');
					c.width = rootView.width;
					c.height = rootView.height;
					var tempCtx = c.getContext('2d');
					tempCtx.fillStyle = g;
					tempSvg.render(tempCtx);		
					return tempCtx.createPattern(c, 'no-repeat');
				}
				
				return g;				
			}
		}
		svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
		
		// linear gradient element
		svg.Element.linearGradient = function(node) {
			this.base = svg.Element.GradientBase;
			this.base(node);
			
			this.getGradient = function(ctx, element) {
				var bb = element.getBoundingBox();
				
				if (!this.attribute('x1').hasValue()
				 && !this.attribute('y1').hasValue()
				 && !this.attribute('x2').hasValue()
				 && !this.attribute('y2').hasValue()) {
					this.attribute('x1', true).value = 0;
					this.attribute('y1', true).value = 0;
					this.attribute('x2', true).value = 1;
					this.attribute('y2', true).value = 0;
				 }
				
				var x1 = (this.gradientUnits == 'objectBoundingBox' 
					? bb.x() + bb.width() * this.attribute('x1').numValue() 
					: this.attribute('x1').toPixels('x'));
				var y1 = (this.gradientUnits == 'objectBoundingBox' 
					? bb.y() + bb.height() * this.attribute('y1').numValue()
					: this.attribute('y1').toPixels('y'));
				var x2 = (this.gradientUnits == 'objectBoundingBox' 
					? bb.x() + bb.width() * this.attribute('x2').numValue()
					: this.attribute('x2').toPixels('x'));
				var y2 = (this.gradientUnits == 'objectBoundingBox' 
					? bb.y() + bb.height() * this.attribute('y2').numValue()
					: this.attribute('y2').toPixels('y'));

				if (x1 == x2 && y1 == y2) return null;
				return ctx.createLinearGradient(x1, y1, x2, y2);
			}
		}
		svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
		
		// radial gradient element
		svg.Element.radialGradient = function(node) {
			this.base = svg.Element.GradientBase;
			this.base(node);
			
			this.getGradient = function(ctx, element) {
				var bb = element.getBoundingBox();
				
				if (!this.attribute('cx').hasValue()) this.attribute('cx', true).value = '50%';
				if (!this.attribute('cy').hasValue()) this.attribute('cy', true).value = '50%';
				if (!this.attribute('r').hasValue()) this.attribute('r', true).value = '50%';
				
				var cx = (this.gradientUnits == 'objectBoundingBox' 
					? bb.x() + bb.width() * this.attribute('cx').numValue() 
					: this.attribute('cx').toPixels('x'));
				var cy = (this.gradientUnits == 'objectBoundingBox' 
					? bb.y() + bb.height() * this.attribute('cy').numValue() 
					: this.attribute('cy').toPixels('y'));
				
				var fx = cx;
				var fy = cy;
				if (this.attribute('fx').hasValue()) {
					fx = (this.gradientUnits == 'objectBoundingBox' 
					? bb.x() + bb.width() * this.attribute('fx').numValue() 
					: this.attribute('fx').toPixels('x'));
				}
				if (this.attribute('fy').hasValue()) {
					fy = (this.gradientUnits == 'objectBoundingBox' 
					? bb.y() + bb.height() * this.attribute('fy').numValue() 
					: this.attribute('fy').toPixels('y'));
				}
				
				var r = (this.gradientUnits == 'objectBoundingBox' 
					? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
					: this.attribute('r').toPixels());
				
				return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);
			}
		}
		svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
		
		// gradient stop element
		svg.Element.stop = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			this.offset = this.attribute('offset').numValue();
			if (this.offset < 0) this.offset = 0;
			if (this.offset > 1) this.offset = 1;
			
			var stopColor = this.style('stop-color');
			if (this.style('stop-opacity').hasValue()) stopColor = stopColor.addOpacity(this.style('stop-opacity').value);
			this.color = stopColor.value;
		}
		svg.Element.stop.prototype = new svg.Element.ElementBase;
		
		// animation base element
		svg.Element.AnimateBase = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			svg.Animations.push(this);
			
			this.duration = 0.0;
			this.begin = this.attribute('begin').toMilliseconds();
			this.maxDuration = this.begin + this.attribute('dur').toMilliseconds();
			
			this.getProperty = function() {
				var attributeType = this.attribute('attributeType').value;
				var attributeName = this.attribute('attributeName').value;
				
				if (attributeType == 'CSS') {
					return this.parent.style(attributeName, true);
				}
				return this.parent.attribute(attributeName, true);			
			};
			
			this.initialValue = null;
			this.initialUnits = '';
			this.removed = false;			

			this.calcValue = function() {
				// OVERRIDE ME!
				return '';
			}
					
			this.update = function(delta) {	
				// set initial value
				if (this.initialValue == null) {
					this.initialValue = this.getProperty().value;
					this.initialUnits = this.getProperty().getUnits();
				}
			
				// if we're past the end time
				if (this.duration > this.maxDuration) {
					// loop for indefinitely repeating animations
					if (this.attribute('repeatCount').value == 'indefinite'
					 || this.attribute('repeatDur').value == 'indefinite') {
						this.duration = 0.0
					}
					else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
						this.removed = true;
						this.getProperty().value = this.initialValue;
						return true;
					}
					else {
						return false; // no updates made
					}
				}			
				this.duration = this.duration + delta;
			
				// if we're past the begin time
				var updated = false;
				if (this.begin < this.duration) {
					var newValue = this.calcValue(); // tween
					
					if (this.attribute('type').hasValue()) {
						// for transform, etc.
						var type = this.attribute('type').value;
						newValue = type + '(' + newValue + ')';
					}
					
					this.getProperty().value = newValue;
					updated = true;
				}
				
				return updated;
			}
			
			this.from = this.attribute('from');
			this.to = this.attribute('to');
			this.values = this.attribute('values');
			if (this.values.hasValue()) this.values.value = this.values.value.split(';');
			
			// fraction of duration we've covered
			this.progress = function() {
				var ret = { progress: (this.duration - this.begin) / (this.maxDuration - this.begin) };
				if (this.values.hasValue()) {
					var p = ret.progress * (this.values.value.length - 1);
					var lb = Math.floor(p), ub = Math.ceil(p);
					ret.from = new svg.Property('from', parseFloat(this.values.value[lb]));
					ret.to = new svg.Property('to', parseFloat(this.values.value[ub]));
					ret.progress = (p - lb) / (ub - lb);
				}
				else {
					ret.from = this.from;
					ret.to = this.to;
				}
				return ret;
			}			
		}
		svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
		
		// animate element
		svg.Element.animate = function(node) {
			this.base = svg.Element.AnimateBase;
			this.base(node);
			
			this.calcValue = function() {
				var p = this.progress();
				
				// tween value linearly
				var newValue = p.from.numValue() + (p.to.numValue() - p.from.numValue()) * p.progress; 
				return newValue + this.initialUnits;
			};
		}
		svg.Element.animate.prototype = new svg.Element.AnimateBase;
			
		// animate color element
		svg.Element.animateColor = function(node) {
			this.base = svg.Element.AnimateBase;
			this.base(node);

			this.calcValue = function() {
				var p = this.progress();
				var from = new RGBColor(p.from.value);
				var to = new RGBColor(p.to.value);
				
				if (from.ok && to.ok) {
					// tween color linearly
					var r = from.r + (to.r - from.r) * p.progress;
					var g = from.g + (to.g - from.g) * p.progress;
					var b = from.b + (to.b - from.b) * p.progress;
					return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
				}
				return this.attribute('from').value;
			};
		}
		svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
		
		// animate transform element
		svg.Element.animateTransform = function(node) {
			this.base = svg.Element.AnimateBase;
			this.base(node);
			
			this.calcValue = function() {
				var p = this.progress();
				
				// tween value linearly
				var from = svg.ToNumberArray(p.from.value);
				var to = svg.ToNumberArray(p.to.value);
				var newValue = '';
				for (var i=0; i<from.length; i++) {
					newValue += from[i] + (to[i] - from[i]) * p.progress + ' ';
				}
				return newValue;
			};
		}
		svg.Element.animateTransform.prototype = new svg.Element.animate;
		
		// font element
		svg.Element.font = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);

			this.horizAdvX = this.attribute('horiz-adv-x').numValue();			
			
			this.isRTL = false;
			this.isArabic = false;
			this.fontFace = null;
			this.missingGlyph = null;
			this.glyphs = [];			
			for (var i=0; i<this.children.length; i++) {
				var child = this.children[i];
				if (child.type == 'font-face') {
					this.fontFace = child;
					if (child.style('font-family').hasValue()) {
						svg.Definitions[child.style('font-family').value] = this;
					}
				}
				else if (child.type == 'missing-glyph') this.missingGlyph = child;
				else if (child.type == 'glyph') {
					if (child.arabicForm != '') {
						this.isRTL = true;
						this.isArabic = true;
						if (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];
						this.glyphs[child.unicode][child.arabicForm] = child;
					}
					else {
						this.glyphs[child.unicode] = child;
					}
				}
			}	
		}
		svg.Element.font.prototype = new svg.Element.ElementBase;
		
		// font-face element
		svg.Element.fontface = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);	
			
			this.ascent = this.attribute('ascent').value;
			this.descent = this.attribute('descent').value;
			this.unitsPerEm = this.attribute('units-per-em').numValue();				
		}
		svg.Element.fontface.prototype = new svg.Element.ElementBase;
		
		// missing-glyph element
		svg.Element.missingglyph = function(node) {
			this.base = svg.Element.path;
			this.base(node);	
			
			this.horizAdvX = 0;
		}
		svg.Element.missingglyph.prototype = new svg.Element.path;
		
		// glyph element
		svg.Element.glyph = function(node) {
			this.base = svg.Element.path;
			this.base(node);	
			
			this.horizAdvX = this.attribute('horiz-adv-x').numValue();
			this.unicode = this.attribute('unicode').value;
			this.arabicForm = this.attribute('arabic-form').value;
		}
		svg.Element.glyph.prototype = new svg.Element.path;
		
		// text element
		svg.Element.text = function(node) {
			this.captureTextNodes = true;
			this.base = svg.Element.RenderedElementBase;
			this.base(node);
			
			this.baseSetContext = this.setContext;
			this.setContext = function(ctx) {
				this.baseSetContext(ctx);
				if (this.style('dominant-baseline').hasValue()) ctx.textBaseline = this.style('dominant-baseline').value;
				if (this.style('alignment-baseline').hasValue()) ctx.textBaseline = this.style('alignment-baseline').value;
			}
			
			this.getBoundingBox = function () {
				// TODO: implement
				return new svg.BoundingBox(this.attribute('x').toPixels('x'), this.attribute('y').toPixels('y'), 0, 0);
			}
			
			this.renderChildren = function(ctx) {
				this.x = this.attribute('x').toPixels('x');
				this.y = this.attribute('y').toPixels('y');
				this.x += this.getAnchorDelta(ctx, this, 0);
				for (var i=0; i<this.children.length; i++) {
					this.renderChild(ctx, this, i);
				}
			}
			
			this.getAnchorDelta = function (ctx, parent, startI) {
				var textAnchor = this.style('text-anchor').valueOrDefault('start');
				if (textAnchor != 'start') {
					var width = 0;
					for (var i=startI; i<parent.children.length; i++) {
						var child = parent.children[i];
						if (i > startI && child.attribute('x').hasValue()) break; // new group
						width += child.measureTextRecursive(ctx);
					}
					return -1 * (textAnchor == 'end' ? width : width / 2.0);
				}
				return 0;
			}
			
			this.renderChild = function(ctx, parent, i) {
				var child = parent.children[i];
				if (child.attribute('x').hasValue()) {
					child.x = child.attribute('x').toPixels('x') + this.getAnchorDelta(ctx, parent, i);
				}
				else {
					if (this.attribute('dx').hasValue()) this.x += this.attribute('dx').toPixels('x');
					if (child.attribute('dx').hasValue()) this.x += child.attribute('dx').toPixels('x');
					child.x = this.x;
				}
				this.x = child.x + child.measureText(ctx);
				
				if (child.attribute('y').hasValue()) {
					child.y = child.attribute('y').toPixels('y');
				}
				else {
					if (this.attribute('dy').hasValue()) this.y += this.attribute('dy').toPixels('y');
					if (child.attribute('dy').hasValue()) this.y += child.attribute('dy').toPixels('y');
					child.y = this.y;
				}
				this.y = child.y;
				
				child.render(ctx);
				
				for (var i=0; i<child.children.length; i++) {
					this.renderChild(ctx, child, i);
				}
			}
		}
		svg.Element.text.prototype = new svg.Element.RenderedElementBase;
		
		// text base
		svg.Element.TextElementBase = function(node) {
			this.base = svg.Element.RenderedElementBase;
			this.base(node);
			
			this.getGlyph = function(font, text, i) {
				var c = text[i];
				var glyph = null;
				if (font.isArabic) {
					var arabicForm = 'isolated';
					if ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal'; 
					if (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';
					if (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';
					if (typeof(font.glyphs[c]) != 'undefined') {
						glyph = font.glyphs[c][arabicForm];
						if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];
					}
				}
				else {
					glyph = font.glyphs[c];
				}
				if (glyph == null) glyph = font.missingGlyph;
				return glyph;
			}
			
			this.renderChildren = function(ctx) {
				var customFont = this.parent.style('font-family').getDefinition();
				if (customFont != null) {
					var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
					var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
					var text = this.getText();
					if (customFont.isRTL) text = text.split("").reverse().join("");
					
					var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
					for (var i=0; i<text.length; i++) {
						var glyph = this.getGlyph(customFont, text, i);
						var scale = fontSize / customFont.fontFace.unitsPerEm;
						ctx.translate(this.x, this.y);
						ctx.scale(scale, -scale);
						var lw = ctx.lineWidth;
						ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;
						if (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);
						glyph.render(ctx);
						if (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);
						ctx.lineWidth = lw;
						ctx.scale(1/scale, -1/scale);
						ctx.translate(-this.x, -this.y);	
						
						this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;
						if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
							this.x += dx[i];
						}
					}
					return;
				}
			
				if (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
				if (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);
			}
			
			this.getText = function() {
				// OVERRIDE ME
			}
			
			this.measureTextRecursive = function(ctx) {
				var width = this.measureText(ctx);
				for (var i=0; i<this.children.length; i++) {
					width += this.children[i].measureTextRecursive(ctx);
				}
				return width;
			}
			
			this.measureText = function(ctx) {
				var customFont = this.parent.style('font-family').getDefinition();
				if (customFont != null) {
					var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
					var measure = 0;
					var text = this.getText();
					if (customFont.isRTL) text = text.split("").reverse().join("");
					var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
					for (var i=0; i<text.length; i++) {
						var glyph = this.getGlyph(customFont, text, i);
						measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;
						if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
							measure += dx[i];
						}
					}
					return measure;
				}
			
				var textToMeasure = svg.compressSpaces(this.getText());
				if (!ctx.measureText) return textToMeasure.length * 10;
				
				ctx.save();
				this.setContext(ctx);
				var width = ctx.measureText(textToMeasure).width;
				ctx.restore();
				return width;
			}
		}
		svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
		
		// tspan 
		svg.Element.tspan = function(node) {
			this.captureTextNodes = true;
			this.base = svg.Element.TextElementBase;
			this.base(node);
			
			this.text = node.nodeValue || node.text || '';
			this.getText = function() {
				return this.text;
			}
		}
		svg.Element.tspan.prototype = new svg.Element.TextElementBase;
		
		// tref
		svg.Element.tref = function(node) {
			this.base = svg.Element.TextElementBase;
			this.base(node);
			
			this.getText = function() {
				var element = this.getHrefAttribute().getDefinition();
				if (element != null) return element.children[0].getText();
			}
		}
		svg.Element.tref.prototype = new svg.Element.TextElementBase;		
		
		// a element
		svg.Element.a = function(node) {
			this.base = svg.Element.TextElementBase;
			this.base(node);
			
			this.hasText = true;
			for (var i=0; i<node.childNodes.length; i++) {
				if (node.childNodes[i].nodeType != 3) this.hasText = false;
			}
			
			// this might contain text
			this.text = this.hasText ? node.childNodes[0].nodeValue : '';
			this.getText = function() {
				return this.text;
			}		

			this.baseRenderChildren = this.renderChildren;
			this.renderChildren = function(ctx) {
				if (this.hasText) {
					// render as text element
					this.baseRenderChildren(ctx);
					var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
					svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.toPixels('y'), this.x + this.measureText(ctx), this.y));					
				}
				else {
					// render as temporary group
					var g = new svg.Element.g();
					g.children = this.children;
					g.parent = this;
					g.render(ctx);
				}
			}
			
			this.onclick = function() {
				window.open(this.getHrefAttribute().value);
			}
			
			this.onmousemove = function() {
				svg.ctx.canvas.style.cursor = 'pointer';
			}
		}
		svg.Element.a.prototype = new svg.Element.TextElementBase;		
		
		// image element
		svg.Element.image = function(node) {
			this.base = svg.Element.RenderedElementBase;
			this.base(node);
			
			var href = this.getHrefAttribute().value;
			var isSvg = href.match(/\.svg$/)
			
			svg.Images.push(this);
			this.loaded = false;
			if (!isSvg) {
				this.img = document.createElement('img');
				var self = this;
				this.img.onload = function() { self.loaded = true; }
				this.img.onerror = function() { if (typeof(console) != 'undefined') { console.log('ERROR: image "' + href + '" not found'); self.loaded = true; } }
				this.img.src = href;
			}
			else {
				this.img = svg.ajax(href);
				this.loaded = true;
			}
			
			this.renderChildren = function(ctx) {
				var x = this.attribute('x').toPixels('x');
				var y = this.attribute('y').toPixels('y');
				
				var width = this.attribute('width').toPixels('x');
				var height = this.attribute('height').toPixels('y');			
				if (width == 0 || height == 0) return;
			
				ctx.save();
				if (isSvg) {
					ctx.drawSvg(this.img, x, y, width, height);
				}
				else {
					ctx.translate(x, y);
					svg.AspectRatio(ctx,
									this.attribute('preserveAspectRatio').value,
									width,
									this.img.width,
									height,
									this.img.height,
									0,
									0);	
					ctx.drawImage(this.img, 0, 0);		
				}
				ctx.restore();
			}
			
			this.getBoundingBox = function() {
				var x = this.attribute('x').toPixels('x');
				var y = this.attribute('y').toPixels('y');
				var width = this.attribute('width').toPixels('x');
				var height = this.attribute('height').toPixels('y');
				return new svg.BoundingBox(x, y, x + width, y + height);
			}
		}
		svg.Element.image.prototype = new svg.Element.RenderedElementBase;
		
		// group element
		svg.Element.g = function(node) {
			this.base = svg.Element.RenderedElementBase;
			this.base(node);
			
			this.getBoundingBox = function() {
				var bb = new svg.BoundingBox();
				for (var i=0; i<this.children.length; i++) {
					bb.addBoundingBox(this.children[i].getBoundingBox());
				}
				return bb;
			};
		}
		svg.Element.g.prototype = new svg.Element.RenderedElementBase;

		// symbol element
		svg.Element.symbol = function(node) {
			this.base = svg.Element.RenderedElementBase;
			this.base(node);
			
			this.baseSetContext = this.setContext;
			this.setContext = function(ctx) {		
				this.baseSetContext(ctx);
				
				// viewbox
				if (this.attribute('viewBox').hasValue()) {				
					var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
					var minX = viewBox[0];
					var minY = viewBox[1];
					width = viewBox[2];
					height = viewBox[3];
					
					svg.AspectRatio(ctx,
									this.attribute('preserveAspectRatio').value, 
									this.attribute('width').toPixels('x'),
									width,
									this.attribute('height').toPixels('y'),
									height,
									minX,
									minY);

					svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);						
				}
			}			
		}
		svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;		
			
		// style element
		svg.Element.style = function(node) { 
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			// text, or spaces then CDATA
			var css = ''
			for (var i=0; i<node.childNodes.length; i++) {
			  css += node.childNodes[i].nodeValue;
			}
			css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, ''); // remove comments
			css = svg.compressSpaces(css); // replace whitespace
			var cssDefs = css.split('}');
			for (var i=0; i<cssDefs.length; i++) {
				if (svg.trim(cssDefs[i]) != '') {
					var cssDef = cssDefs[i].split('{');
					var cssClasses = cssDef[0].split(',');
					var cssProps = cssDef[1].split(';');
					for (var j=0; j<cssClasses.length; j++) {
						var cssClass = svg.trim(cssClasses[j]);
						if (cssClass != '') {
							var props = {};
							for (var k=0; k<cssProps.length; k++) {
								var prop = cssProps[k].indexOf(':');
								var name = cssProps[k].substr(0, prop);
								var value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);
								if (name != null && value != null) {
									props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));
								}
							}
							svg.Styles[cssClass] = props;
							if (cssClass == '@font-face') {
								var fontFamily = props['font-family'].value.replace(/"/g,'');
								var srcs = props['src'].value.split(',');
								for (var s=0; s<srcs.length; s++) {
									if (srcs[s].indexOf('format("svg")') > 0) {
										var urlStart = srcs[s].indexOf('url');
										var urlEnd = srcs[s].indexOf(')', urlStart);
										var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);
										var doc = svg.parseXml(svg.ajax(url));
										var fonts = doc.getElementsByTagName('font');
										for (var f=0; f<fonts.length; f++) {
											var font = svg.CreateElement(fonts[f]);
											svg.Definitions[fontFamily] = font;
										}
									}
								}
							}
						}
					}
				}
			}
		}
		svg.Element.style.prototype = new svg.Element.ElementBase;
		
		// use element 
		svg.Element.use = function(node) {
			this.base = svg.Element.RenderedElementBase;
			this.base(node);
			
			this.baseSetContext = this.setContext;
			this.setContext = function(ctx) {
				this.baseSetContext(ctx);
				if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').toPixels('x'), 0);
				if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').toPixels('y'));
			}
			
			this.getDefinition = function() {
				var element = this.getHrefAttribute().getDefinition();
				if (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;
				if (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;
				return element;
			}
			
			this.path = function(ctx) {
				var element = this.getDefinition();
				if (element != null) element.path(ctx);
			}
			
			this.getBoundingBox = function() {
				var element = this.getDefinition();
				if (element != null) return element.getBoundingBox();
			}
			
			this.renderChildren = function(ctx) {
				var element = this.getDefinition();
				if (element != null) {
					// temporarily detach from parent and render
					var oldParent = element.parent;
					element.parent = null;
					element.render(ctx);
					element.parent = oldParent;
				}
			}
		}
		svg.Element.use.prototype = new svg.Element.RenderedElementBase;
		
		// mask element
		svg.Element.mask = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
						
			this.apply = function(ctx, element) {
				// render as temp svg	
				var x = this.attribute('x').toPixels('x');
				var y = this.attribute('y').toPixels('y');
				var width = this.attribute('width').toPixels('x');
				var height = this.attribute('height').toPixels('y');
				
				if (width == 0 && height == 0) {
					var bb = new svg.BoundingBox();
					for (var i=0; i<this.children.length; i++) {
						bb.addBoundingBox(this.children[i].getBoundingBox());
					}
					var x = Math.floor(bb.x1);
					var y = Math.floor(bb.y1);
					var width = Math.floor(bb.width());
					var	height = Math.floor(bb.height());
				}
				
				// temporarily remove mask to avoid recursion
				var mask = element.attribute('mask').value;
				element.attribute('mask').value = '';
				
					var cMask = document.createElement('canvas');
					cMask.width = x + width;
					cMask.height = y + height;
					var maskCtx = cMask.getContext('2d');
					this.renderChildren(maskCtx);
				
					var c = document.createElement('canvas');
					c.width = x + width;
					c.height = y + height;
					var tempCtx = c.getContext('2d');
					element.render(tempCtx);
					tempCtx.globalCompositeOperation = 'destination-in';
					tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');
					tempCtx.fillRect(0, 0, x + width, y + height);
					
					ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');
					ctx.fillRect(0, 0, x + width, y + height);
					
				// reassign mask
				element.attribute('mask').value = mask;	
			}
			
			this.render = function(ctx) {
				// NO RENDER
			}
		}
		svg.Element.mask.prototype = new svg.Element.ElementBase;
		
		// clip element
		svg.Element.clipPath = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			this.apply = function(ctx) {
				for (var i=0; i<this.children.length; i++) {
					var child = this.children[i];
					if (typeof(child.path) != 'undefined') {
						var transform = null;
						if (child.attribute('transform').hasValue()) { 
							transform = new svg.Transform(child.attribute('transform').value);
							transform.apply(ctx);
						}
						child.path(ctx);
						ctx.clip();
						if (transform) { transform.unapply(ctx); }
					}
				}
			}
			
			this.render = function(ctx) {
				// NO RENDER
			}
		}
		svg.Element.clipPath.prototype = new svg.Element.ElementBase;

		// filters
		svg.Element.filter = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
						
			this.apply = function(ctx, element) {
				// render as temp svg	
				var bb = element.getBoundingBox();
				var x = Math.floor(bb.x1);
				var y = Math.floor(bb.y1);
				var width = Math.floor(bb.width());
				var	height = Math.floor(bb.height());

				// temporarily remove filter to avoid recursion
				var filter = element.style('filter').value;
				element.style('filter').value = '';
				
				var px = 0, py = 0;
				for (var i=0; i<this.children.length; i++) {
					var efd = this.children[i].extraFilterDistance || 0;
					px = Math.max(px, efd);
					py = Math.max(py, efd);
				}
				
				var c = document.createElement('canvas');
				c.width = width + 2*px;
				c.height = height + 2*py;
				var tempCtx = c.getContext('2d');
				tempCtx.translate(-x + px, -y + py);
				element.render(tempCtx);
			
				// apply filters
				for (var i=0; i<this.children.length; i++) {
					this.children[i].apply(tempCtx, 0, 0, width + 2*px, height + 2*py);
				}
				
				// render on me
				ctx.drawImage(c, 0, 0, width + 2*px, height + 2*py, x - px, y - py, width + 2*px, height + 2*py);
				
				// reassign filter
				element.style('filter', true).value = filter;	
			}
			
			this.render = function(ctx) {
				// NO RENDER
			}		
		}
		svg.Element.filter.prototype = new svg.Element.ElementBase;
		
		svg.Element.feMorphology = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			this.apply = function(ctx, x, y, width, height) {
				// TODO: implement
			}
		}
		svg.Element.feMorphology.prototype = new svg.Element.ElementBase;
		
		svg.Element.feColorMatrix = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);
			
			function imGet(img, x, y, width, height, rgba) {
				return img[y*width*4 + x*4 + rgba];
			}
			
			function imSet(img, x, y, width, height, rgba, val) {
				img[y*width*4 + x*4 + rgba] = val;
			}
			
			this.apply = function(ctx, x, y, width, height) {
				// only supporting grayscale for now per Issue 195, need to extend to all matrix
				// assuming x==0 && y==0 for now
				var srcData = ctx.getImageData(0, 0, width, height);
				for (var y = 0; y < height; y++) {
					for (var x = 0; x < width; x++) {
						var r = imGet(srcData.data, x, y, width, height, 0);
						var g = imGet(srcData.data, x, y, width, height, 1);
						var b = imGet(srcData.data, x, y, width, height, 2);
						var gray = (r + g + b) / 3;
						imSet(srcData.data, x, y, width, height, 0, gray);
						imSet(srcData.data, x, y, width, height, 1, gray);
						imSet(srcData.data, x, y, width, height, 2, gray);
					}
				}
				ctx.clearRect(0, 0, width, height);
				ctx.putImageData(srcData, 0, 0);
			}
		}
		svg.Element.feColorMatrix.prototype = new svg.Element.ElementBase;
		
		svg.Element.feGaussianBlur = function(node) {
			this.base = svg.Element.ElementBase;
			this.base(node);

			this.blurRadius = Math.floor(this.attribute('stdDeviation').numValue());
			this.extraFilterDistance = this.blurRadius;
			
			this.apply = function(ctx, x, y, width, height) {
				if (typeof(stackBlurCanvasRGBA) == 'undefined') {
					if (typeof(console) != 'undefined') { console.log('ERROR: StackBlur.js must be included for blur to work'); }
					return;
				}
				
				// StackBlur requires canvas be on document
				ctx.canvas.id = svg.UniqueId();
				ctx.canvas.style.display = 'none';
				document.body.appendChild(ctx.canvas);
				stackBlurCanvasRGBA(ctx.canvas.id, x, y, width, height, this.blurRadius);
				document.body.removeChild(ctx.canvas);
			}
		}
		svg.Element.feGaussianBlur.prototype = new svg.Element.ElementBase;
		
		// title element, do nothing
		svg.Element.title = function(node) {
		}
		svg.Element.title.prototype = new svg.Element.ElementBase;

		// desc element, do nothing
		svg.Element.desc = function(node) {
		}
		svg.Element.desc.prototype = new svg.Element.ElementBase;		
		
		svg.Element.MISSING = function(node) {
			if (typeof(console) != 'undefined') { console.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.'); }
		}
		svg.Element.MISSING.prototype = new svg.Element.ElementBase;
		
		// element factory
		svg.CreateElement = function(node) {	
			var className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace
			className = className.replace(/\-/g,''); // remove dashes
			var e = null;
			if (typeof(svg.Element[className]) != 'undefined') {
				e = new svg.Element[className](node);
			}
			else {
				e = new svg.Element.MISSING(node);
			}

			e.type = node.nodeName;
			return e;
		}
				
		// load from url
		svg.load = function(ctx, url) {
			svg.loadXml(ctx, svg.ajax(url));
		}
		
		// load from xml
		svg.loadXml = function(ctx, xml) {
			svg.loadXmlDoc(ctx, svg.parseXml(xml));
		}
		
		svg.loadXmlDoc = function(ctx, dom) {
			svg.init(ctx);
			
			var mapXY = function(p) {
				var e = ctx.canvas;
				while (e) {
					p.x -= e.offsetLeft;
					p.y -= e.offsetTop;
					e = e.offsetParent;
				}
				if (window.scrollX) p.x += window.scrollX;
				if (window.scrollY) p.y += window.scrollY;
				return p;
			}
			
			// bind mouse
			if (svg.opts['ignoreMouse'] != true) {
				ctx.canvas.onclick = function(e) {
					var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
					svg.Mouse.onclick(p.x, p.y);
				};
				ctx.canvas.onmousemove = function(e) {
					var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
					svg.Mouse.onmousemove(p.x, p.y);
				};
			}
		
			var e = svg.CreateElement(dom.documentElement);
			e.root = true;
					
			// render loop
			var isFirstRender = true;
			var draw = function() {
				svg.ViewPort.Clear();
				if (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);
			
				if (svg.opts['ignoreDimensions'] != true) {
					// set canvas size
					if (e.style('width').hasValue()) {
						ctx.canvas.width = e.style('width').toPixels('x');
						ctx.canvas.style.width = ctx.canvas.width + 'px';
					}
					if (e.style('height').hasValue()) {
						ctx.canvas.height = e.style('height').toPixels('y');
						ctx.canvas.style.height = ctx.canvas.height + 'px';
					}
				}
				var cWidth = ctx.canvas.clientWidth || ctx.canvas.width;
				var cHeight = ctx.canvas.clientHeight || ctx.canvas.height;
				if (svg.opts['ignoreDimensions'] == true && e.style('width').hasValue() && e.style('height').hasValue()) {
					cWidth = e.style('width').toPixels('x');
					cHeight = e.style('height').toPixels('y');
				}
				svg.ViewPort.SetCurrent(cWidth, cHeight);		
				
				if (svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
				if (svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
				if (svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {
					var xRatio = 1, yRatio = 1, viewBox = svg.ToNumberArray(e.attribute('viewBox').value);
					if (e.attribute('width').hasValue()) xRatio = e.attribute('width').toPixels('x') / svg.opts['scaleWidth'];
					else if (!isNaN(viewBox[2])) xRatio = viewBox[2] / svg.opts['scaleWidth'];
					if (e.attribute('height').hasValue()) yRatio = e.attribute('height').toPixels('y') / svg.opts['scaleHeight'];
					else if (!isNaN(viewBox[3])) yRatio = viewBox[3] / svg.opts['scaleHeight'];
					
					e.attribute('width', true).value = svg.opts['scaleWidth'];
					e.attribute('height', true).value = svg.opts['scaleHeight'];			
					e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);
					e.attribute('preserveAspectRatio', true).value = 'none';
				}
			
				// clear and render
				if (svg.opts['ignoreClear'] != true) {
					ctx.clearRect(0, 0, cWidth, cHeight);
				}
				e.render(ctx);
				if (isFirstRender) {
					isFirstRender = false;
					if (typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback'](dom);
				}			
			}
			
			var waitingForImages = true;
			if (svg.ImagesLoaded()) {
				waitingForImages = false;
				draw();
			}
			svg.intervalID = setInterval(function() { 
				var needUpdate = false;
				
				if (waitingForImages && svg.ImagesLoaded()) {
					waitingForImages = false;
					needUpdate = true;
				}
			
				// need update from mouse events?
				if (svg.opts['ignoreMouse'] != true) {
					needUpdate = needUpdate | svg.Mouse.hasEvents();
				}
			
				// need update from animations?
				if (svg.opts['ignoreAnimation'] != true) {
					for (var i=0; i<svg.Animations.length; i++) {
						needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
					}
				}
				
				// need update from redraw?
				if (typeof(svg.opts['forceRedraw']) == 'function') {
					if (svg.opts['forceRedraw']() == true) needUpdate = true;
				}
				
				// render if needed
				if (needUpdate) {
					draw();				
					svg.Mouse.runEvents(); // run and clear our events
				}
			}, 1000 / svg.FRAMERATE);
		}
		
		svg.stop = function() {
			if (svg.intervalID) {
				clearInterval(svg.intervalID);
			}
		}
		
		svg.Mouse = new (function() {
			this.events = [];
			this.hasEvents = function() { return this.events.length != 0; }
		
			this.onclick = function(x, y) {
				this.events.push({ type: 'onclick', x: x, y: y, 
					run: function(e) { if (e.onclick) e.onclick(); }
				});
			}
			
			this.onmousemove = function(x, y) {
				this.events.push({ type: 'onmousemove', x: x, y: y,
					run: function(e) { if (e.onmousemove) e.onmousemove(); }
				});
			}			
			
			this.eventElements = [];
			
			this.checkPath = function(element, ctx) {
				for (var i=0; i<this.events.length; i++) {
					var e = this.events[i];
					if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
				}
			}
			
			this.checkBoundingBox = function(element, bb) {
				for (var i=0; i<this.events.length; i++) {
					var e = this.events[i];
					if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
				}			
			}
			
			this.runEvents = function() {
				svg.ctx.canvas.style.cursor = '';
				
				for (var i=0; i<this.events.length; i++) {
					var e = this.events[i];
					var element = this.eventElements[i];
					while (element) {
						e.run(element);
						element = element.parent;
					}
				}		
			
				// done running, clear
				this.events = []; 
				this.eventElements = [];
			}
		});
		
		return svg;
	}
})();

if (typeof(CanvasRenderingContext2D) != 'undefined') {
	CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
		canvg(this.canvas, s, { 
			ignoreMouse: true, 
			ignoreAnimation: true, 
			ignoreDimensions: true, 
			ignoreClear: true, 
			offsetX: dx, 
			offsetY: dy, 
			scaleWidth: dw, 
			scaleHeight: dh
		});
	}
};
define("canvg", ["rgbcolor"], (function (global) {
    return function () {
        var ret, fn;
        return ret || global.canvg;
    };
}(this)));

/*global define: false, d3: false */

/**
 * Tiny module providing useful functions for color manipulation.
 */
define('common/views/color',[],function () {

  function d3rgba(color) {
    // Use regexp to parse rgba if it's necessary.
    var rgba = color.match(/rgba\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+(?:\.\d+)?)\)/i);
    if (rgba !== null) {
      return {rgb: d3.rgb(rgba[1], rgba[2], rgba[3]), a: Number(rgba[4])};
    } else {
      return {rgb: d3.rgb(color), a: 1};
    }
  }

  return {
    /**
     * d3.rgb is handy, however it cannot parse RGBA colors.
     * @param  {string} color Web-compatible color definition (e.g. "red", "#ff0012", "#000", rgba(10,10,10,0.5).
     * @return {object} Object containing two properties: `rgb` - d3.rgb color and `a` - alpha value.
     */
    d3rgba: d3rgba,

    /**
     * Returns color contrasting to specified background color (black or white).
     * Note that if background color specifies alpha channel (e.g. rgba(0,0,0,0.5)),
     * it will be ignored!
     * @param  {string} bg Web-compatible color definition (e.g. "red", "#ff0012", "#000").
     * @return {string} Contrasting color - "#000" or "#fff".
     */
    contrastingColor: function (bg) {
      bg = d3rgba(bg).rgb;
      // Calculate luminance in YIQ color space.
      // This ensures that color will be visible on background.
      // This simple algorithm is described here:
      // http://www.w3.org/TR/AERT#color-contrast
      return (bg.r * 299 + bg.g * 587 + bg.b * 114) / 1000 >= 128 ? '#000' : '#fff';
    },

    /**
     * Converts color string to number.
     * @param  {string} colorString Web-compatible color definition (e.g. "red", "#ff0012", "#000").
     * @return {number} Numeric value, e.g. 0xff000 when argument is "#f00", "#ff0000" or "red".
     */
    color2number: function (colorString) {
      return parseInt(d3rgba(colorString).rgb.toString().substr(1), 16);
    }
  };
});



/*
Simple module which provides context menu for amino acids. It allows
to dynamically change type of amino acids in a convenient way.
It uses jQuery.contextMenu plug-in.

CSS style definition: sass/lab/_context-menu.sass
*/


(function() {

  define('cs!models/md2d/views/aminoacid-context-menu',['require','cs!models/md2d/models/aminoacids-helper'],function(require) {
    var HYDROPHILIC_CAT_CLASS, HYDROPHILIC_CLASS, HYDROPHOBIC_CAT_CLASS, HYDROPHOBIC_CLASS, MARKED_CLASS, MENU_CLASS, NEG_CHARGE_CLASS, POS_CHARGE_CLASS, aminoacids, showCategory;
    aminoacids = require('cs!models/md2d/models/aminoacids-helper');
    MENU_CLASS = "aminoacids-menu";
    HYDROPHOBIC_CLASS = "hydrophobic";
    HYDROPHOBIC_CAT_CLASS = "hydrophobic-category";
    HYDROPHILIC_CLASS = "hydrophilic";
    HYDROPHILIC_CAT_CLASS = "hydrophilic-category";
    POS_CHARGE_CLASS = "pos-charge";
    NEG_CHARGE_CLASS = "neg-charge";
    MARKED_CLASS = "marked";
    showCategory = function(type, animate) {
      var func;
      func = {
        show: animate ? "slideDown" : "show",
        hide: animate ? "slideUp" : "hide"
      };
      if (type === "hydrophobic") {
        $("." + HYDROPHOBIC_CLASS)[func.show]();
        $("." + HYDROPHILIC_CLASS)[func.hide]();
        $("." + HYDROPHOBIC_CAT_CLASS).addClass("expanded");
        return $("." + HYDROPHILIC_CAT_CLASS).removeClass("expanded");
      } else {
        $("." + HYDROPHOBIC_CLASS)[func.hide]();
        $("." + HYDROPHILIC_CLASS)[func.show]();
        $("." + HYDROPHOBIC_CAT_CLASS).removeClass("expanded");
        return $("." + HYDROPHILIC_CAT_CLASS).addClass("expanded");
      }
    };
    return {
      /*
        Register context menu for DOM elements defined by @selector.
        @model, @view are associated model and view, used to set
        properties and redraw view. @getClickedAtom should return data
        of the clicked atom.
      */

      register: function(model, view, selector, getClickedAtom) {
        var i18n;
        i18n = view.i18n;
        $.contextMenu("destroy", selector);
        $.contextMenu({
          selector: selector,
          appendTo: ".lab-responsive-content",
          className: MENU_CLASS,
          animation: {
            show: "show",
            hide: "hide"
          },
          trigger: "left",
          callback: function(key, options) {
            var elemId, marked, props;
            props = getClickedAtom();
            marked = aminoacids.getAminoAcidByElement(props.element).abbreviation;
            options.items[marked].$node.removeClass(MARKED_CLASS);
            elemId = aminoacids.abbrToElement(key);
            model.setAtomProperties(props.idx, {
              element: elemId
            });
            return view.repaint();
          },
          position: function(opt, x, y) {
            var $win, bottom, height, offset, right, triggerIsFixed, width;
            $win = $(window);
            if (!x && !y) {
              opt.determinePosition.call(this, opt.$menu);
              return;
            } else if (x === "maintain" && y === "maintain") {
              offset = opt.$menu.position();
            } else {
              triggerIsFixed = opt.$trigger.parents().andSelf().filter(function() {
                return $(this).css('position') === "fixed";
              }).length;
              if (triggerIsFixed) {
                y -= $win.scrollTop();
                x -= $win.scrollLeft();
              }
              offset = {
                top: y,
                left: x
              };
            }
            bottom = $win.scrollTop() + $win.height();
            right = $win.scrollLeft() + $win.width();
            /*
                    !!! Workaround for the correct positioning:
                    Use scrollHeight / scrollWidth as these functions return correct height / width
                    in contrast to opt.$menu.height() / opt.$menu.width().
            */

            height = opt.$menu[0].scrollHeight;
            width = opt.$menu[0].scrollWidth;
            if (offset.top + height > bottom) {
              offset.top -= height;
            }
            if (offset.left + width > right) {
              offset.left -= width;
            }
            offset.left += 1;
            return opt.$menu.css(offset);
          },
          events: {
            show: function(options) {
              var $node, aminoAcidProps, atom, key;
              atom = getClickedAtom();
              aminoAcidProps = atom && aminoacids.getAminoAcidByElement(atom.element);
              if (!atom || !aminoAcidProps) {
                return false;
              }
              key = aminoAcidProps.abbreviation;
              $node = options.items[key].$node;
              $node.addClass(MARKED_CLASS);
              if ($node.hasClass(HYDROPHOBIC_CLASS)) {
                showCategory("hydrophobic");
              } else {
                showCategory("hydrophilic");
              }
              return true;
            },
            hide: function(options) {
              var key, props;
              props = getClickedAtom();
              key = aminoacids.getAminoAcidByElement(props.element).abbreviation;
              options.items[key].$node.removeClass(MARKED_CLASS);
              return true;
            }
          },
          items: {
            "Hydrophobic": {
              name: i18n.t("md2d.aminoacid_menu.hydrophobic"),
              className: "" + HYDROPHOBIC_CAT_CLASS,
              callback: function() {
                showCategory("hydrophobic", true);
                return false;
              }
            },
            "Gly": {
              name: i18n.t("md2d.aminoacid_menu.glycine"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Ala": {
              name: i18n.t("md2d.aminoacid_menu.alanine"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Val": {
              name: i18n.t("md2d.aminoacid_menu.valine"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Leu": {
              name: i18n.t("md2d.aminoacid_menu.leucine"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Ile": {
              name: i18n.t("md2d.aminoacid_menu.isoleucine"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Phe": {
              name: i18n.t("md2d.aminoacid_menu.phenylalanine"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Pro": {
              name: i18n.t("md2d.aminoacid_menu.proline"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Trp": {
              name: i18n.t("md2d.aminoacid_menu.tryptophan"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Met": {
              name: i18n.t("md2d.aminoacid_menu.methionine"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Cys": {
              name: i18n.t("md2d.aminoacid_menu.cysteine"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Tyr": {
              name: i18n.t("md2d.aminoacid_menu.tyrosine"),
              className: "" + HYDROPHOBIC_CLASS
            },
            "Hydrophilic": {
              name: i18n.t("md2d.aminoacid_menu.hydrophilic"),
              className: "" + HYDROPHILIC_CAT_CLASS,
              callback: function() {
                showCategory("hydrophilic", true);
                return false;
              }
            },
            "Asn": {
              name: i18n.t("md2d.aminoacid_menu.asparagine"),
              className: "" + HYDROPHILIC_CLASS
            },
            "Gln": {
              name: i18n.t("md2d.aminoacid_menu.glutamine"),
              className: "" + HYDROPHILIC_CLASS
            },
            "Ser": {
              name: i18n.t("md2d.aminoacid_menu.serine"),
              className: "" + HYDROPHILIC_CLASS
            },
            "Thr": {
              name: i18n.t("md2d.aminoacid_menu.threonine"),
              className: "" + HYDROPHILIC_CLASS
            },
            "Asp": {
              name: i18n.t("md2d.aminoacid_menu.asparticacid"),
              className: "" + HYDROPHILIC_CLASS + " " + NEG_CHARGE_CLASS
            },
            "Glu": {
              name: i18n.t("md2d.aminoacid_menu.glutamicacid"),
              className: "" + HYDROPHILIC_CLASS + " " + NEG_CHARGE_CLASS
            },
            "Lys": {
              name: i18n.t("md2d.aminoacid_menu.lysine"),
              className: "" + HYDROPHILIC_CLASS + " " + POS_CHARGE_CLASS
            },
            "Arg": {
              name: i18n.t("md2d.aminoacid_menu.arginine"),
              className: "" + HYDROPHILIC_CLASS + " " + POS_CHARGE_CLASS
            },
            "His": {
              name: i18n.t("md2d.aminoacid_menu.histidine"),
              className: "" + HYDROPHILIC_CLASS + " " + POS_CHARGE_CLASS
            }
          }
        });
        return showCategory("hydrophobic");
      }
    };
  });

}).call(this);

/*global define: false */

define('models/md2d/views/atoms-interactions',['require','common/alert','cs!models/md2d/views/aminoacid-context-menu'],function(require) {
  // Dependencies.
  var alert               = require('common/alert'),
      amniacidContextMenu = require('cs!models/md2d/views/aminoacid-context-menu'),

      POINT_CACHE = {},

      TRANSLATE = 'translate',
      ROTATE = 'rotate';

  function getAngle(cx, cy, x, y) {
    return Math.atan2(x - cx, y - cy);
  }

  function rotate(x, y, angle) {
    return {
      x: x * Math.cos(angle) - y * Math.sin(angle),
      y: x * Math.sin(angle) + y * Math.cos(angle)
    };
  }

  return function AtomsInteractions(modelView, model, target) {
    var api,

        m2px,
        m2pxInv,

        atoms,
        modelWidth,
        modelHeight,

        $target,
        targetOffset,
        targetOversampling,
        viewportX,
        viewportY,

        downAtom,
        contextMenuAtom,
        dragging, dragged;

    //**********************************************************************************************
    // Event handlers related to particular atom:
    function mouseDownHandler(x, y, atom, e) {
      // Dragging is only allowed when user touches an atom or uses *left* mouse button (== 0).
      // Right mouse button can interfere with context menus.
      if (e.button === 0) {
        var mode = null;
        if (isAtomRotatable(atom)) {
          // When atom can be rotated (which means it's part of the molecule) we have more options.
          // 'onAtomDrag' model property defines preferred drag behavior.
          var modes = model.get('onAtomDrag') === TRANSLATE ? [TRANSLATE, ROTATE] : [ROTATE, TRANSLATE];
          // Option key can activate non-default drag behavior.
          mode = e.altKey ? modes[1] : modes[0];
        } else if (isAtomDraggable(atom)) {
          mode = TRANSLATE;
        }
        if (mode !== null) {
          dragBehavior(downAtom, mode);
        }
      }
    }

    function mouseOverHandler(x, y, atom, e) {
      // noop
    }

    function mouseOutHandler(x, y, e) {
      // noop
    }

    function mouseUpHandler(x, y, atom, e) {
      // noop
    }

    function clickHandler(x, y, atom, e) {
      // Custom click handlers for atoms are not supposed to be triggered if the atom was dragged
      if (!dragged && modelView.clickHandler[".atom"]) {
        modelView.clickHandler[".atom"](x, y, atom, atom.idx);
      }
    }

    function contextMenuHandler(x, y, atom, e) {
      // noop
    }

    //**********************************************************************************************
    // Event handlers related to whole target element (canvas):
    function mouseDownCanvas(e) {
      var p = getClickCoords(e);

      downAtom = getAtomUnder(p.x, p.y);
      contextMenuAtom = null;
      dragged = false;

      modelView.hitTestCallback(!!downAtom);
      if (downAtom) {
        mouseDownHandler(p.x, p.y, downAtom, e);
      }
    }

    function mouseMoveCanvas(e) {
      var p = getClickCoords(e),
          atom = getAtomUnder(p.x, p.y);

      modelView.hitTestCallback(!!atom);
      if (atom) {
        mouseOverHandler(p.x, p.y, atom, e);
      } else {
        mouseOutHandler(p.x, p.y, e);
      }
      if (!dragging) {
        setCursorForAtom(atom);
      }
    }

    function mouseUpCanvas(e) {
      var p = getClickCoords(e),
          upAtom = getAtomUnder(p.x, p.y),
          isDOMClick = false;

      modelView.hitTestCallback(!!upAtom);

      if (upAtom) {
        mouseUpHandler(p.x, p.y, upAtom, e);
        if (upAtom === downAtom) {
          // Regardless of whether or not the atom was dragged, if mouseup target == mousedown
          // target we should issue a DOM click event.
          isDOMClick = true;
          clickHandler(p.x, p.y, downAtom);
        }
      }

      modelView.mouseupCallback(isDOMClick);
      downAtom = null;
    }

    function mouseOverCanvas(e) {
      // noop
    }

    function mouseOutCanvas(e) {
      var p = getClickCoords(e);
      mouseOutHandler(p.x, p.y, e);
      setCursor("auto");
    }

    function contextMenuCanvas(e) {
      var p = getClickCoords(e);

      contextMenuAtom = !dragged && getAtomUnder(p.x, p.y);

      modelView.hitTestCallback(!!contextMenuAtom);
      if (contextMenuAtom) {
        contextMenuHandler(p.x, p.y, contextMenuAtom);
      }
    }
    //**********************************************************************************************

    function setCursorFromEvent(e) {
      // If pointer is over some other element just restore the "auto" pointer.
      if (e.target !== target) {
        setCursor("auto");
        return;
      }
      var p = getClickCoords(e);
      setCursorForAtom(getAtomUnder(p.x, p.y));
    }

    function isAtomDraggable(atom) {
      if ( ! atom ) {
        return false;
      }
      if (model.isStopped()) {
        return atom.draggableWhenStopped || model.properties.isBeingEdited;
      }
      return atom.draggable;
    }

    function isAtomRotatable(atom) {
      if (!atom) {
        return false;
      }
      // Note that getMoleculeAtoms doesn't include atom index that we provide, so > 0 means it's part of the molecule.
      // We also require atom to be draggable since the rotation is triggered by dragging.
      return isAtomDraggable(atom) && model.isStopped() && model.getMoleculeAtoms(atom.idx).length > 0;
    }

    function setCursorForAtom(atom) {
      if (isAtomDraggable(atom)) {
        setCursor("move");
      } else {
        setCursor("auto");
      }
    }

    var cursorVal;
    function setCursor(name) {
      if (cursorVal !== name) {
        cursorVal = name;
        document.documentElement.style.cursor = name;
      }
    }

    function init() {
      m2px = modelView.model2canvas;
      m2pxInv = modelView.model2canvasInv;

      $target = $(target);
      $target.addClass("atoms-interaction-layer");

      $target.on("mousedown.atoms-interactions", mouseDownCanvas);
      $target.on("mouseup.atoms-interactions", mouseUpCanvas);
      $target.on("mousemove.atoms-interactions", mouseMoveCanvas);
      $target.on("mouseover.atoms-interactions", mouseOverCanvas);
      $target.on("mouseout.atoms-interactions", mouseOutCanvas);
      $target.on("contextmenu.atoms-interactions", contextMenuCanvas);

      api.bindModel(model);
    }

    function getAtomUnder(x, y) {
      var atom, ax, ay, ar;
      // Very important - start from the last atom. Order of atoms defines order of rendering.
      for (var i = atoms.length - 1; i >= 0; i--) {
        atom = atoms[i];
        ax = atom.x;
        ay = atom.y;
        ar = atom.radius;
        // Optimization: hit area is square.
        if (x > ax - ar && x < ax + ar && y > ay - ar && y < ay + ar) {
          return atom;
        }
      }
      return null;
    }

    function getClickCoords(e, useCachedDimensionsAndViewport) {
      if (!useCachedDimensionsAndViewport) {
        // Sometimes we can risk and assume that model view wasn't resized or view port properties
        // changed (e.g. during atom dragging).
        targetOffset = $target.offset();
        targetOversampling = $target.attr("width") / $target.width();
        // Undefined is a perfectly correct value for view port coords, it means that the whole
        // model area is being displayed.
        viewportX = model.get("viewPortX") || 0;
        viewportY = model.get("viewPortY") || 0;
      }

      POINT_CACHE.x = m2px.invert((e.pageX - targetOffset.left) * targetOversampling) + viewportX;
      POINT_CACHE.y = m2pxInv.invert((e.pageY - targetOffset.top) * targetOversampling) + viewportY;
      return POINT_CACHE;
    }

    function dragBehavior(atom, mode) {
      var translate = mode === TRANSLATE,
          originalPositions, prevAngle, molecule, cx, cy;

      $(window).on("mousemove.lab-drag", function (e) {
        // Prevent accidental text selection or another unwanted action while dragging.
        e.preventDefault();

        // We can use cached canvas dimensions, as they rather don't change between mousedown
        // and mousemove.
        var p = getClickCoords(e, true);
        var x = p.x;
        var y = p.y;

        if (!dragged) {
          // Lazily initialize drag process when user really drags an atom (not only clicks it).
          originalPositions = getMoleculePositions(atom);
          if (translate) {
            if (!model.isStopped() && atom.draggable) {
              model.liveDragStart(atom.idx);
            }
          } else { // rotate
            molecule = model.getMoleculeAtoms(atom.idx).concat(atom.idx);
            var bbox = model.getMoleculeBoundingBox(atom.idx);
            // A bit confusing, but bounding box returns values relative to the center of provided atom.
            cx = (atom.x + bbox.left + atom.x + bbox.right) * 0.5;
            cy = (atom.y + bbox.top + atom.y + bbox.bottom) * 0.5;
            prevAngle = getAngle(cx, cy, x, y);
          }
          dragging = true;
          dragged = true;
        }

        if (translate) {
          translateMolecule(atom, x, y);
        } else { // rotate
          var newAngle = getAngle(cx, cy, x, y);
          rotateMolecule(molecule, cx, cy, prevAngle - newAngle);
          prevAngle = newAngle;
        }
        setCursor("move");
        // Custom drag handler. Note that it works both for translation and rotation.
        if (modelView.dragHandler.atom) {
          modelView.dragHandler.atom(x, y, atom, atom.idx);
        }
        modelView.update();
      }).on("selectstart.lab-drag", function (e) {
        // Disable selection behavior while dragging an atom. It's supported and required in IE and
        // Safari. In Chrome it's enough to call .preventDefault() on mousemove event.
        e.preventDefault();
      }).one("mouseup.lab-drag", function (e) {
        $(window).off(".lab-drag");
        // If user only clicked an atom (mousedown + mouseup, no mousemove), nothing to do.
        if (!dragged) return;
        dragging = false;
        // Prevent accidental text selection or another unwanted action while dragging.
        e.preventDefault();
        // Pointer can be over atom or not (e.g. when user finished dragging below other object).
        setCursorFromEvent(e);

        if (translate && !model.isStopped()) {
          model.liveDragEnd();
        }

        // Validate final position.
        if (model.isStopped()) {
          if (!isPositionValid(atom.idx)) {
            alert(modelView.i18n.t('md2d.invalid_object_position_alert'));
            restoreMoleculePositions(originalPositions);
            modelView.update();
          }
        }
      });
    }

    function getMoleculePositions(atom) {
      var molecule = model.getMoleculeAtoms(atom.idx).concat(atom.idx);
      return molecule.map(function (idx) {
        var atom = model.getAtomProperties(idx);
        return {
          idx: idx,
          x: atom.x,
          y: atom.y
        }
      });
    }

    function restoreMoleculePositions(moleculePositions) {
      moleculePositions.forEach(function (data) {
        setAtomPosition(data.idx, data.x, data.y);
      });
    }

    function translateMolecule(atom, x, y) {
      var bbox = model.getMoleculeBoundingBox(atom.idx);
      if (bbox.left + x < 0) x = 0 - bbox.left;
      if (bbox.right + x > modelWidth) x = modelWidth - bbox.right;
      if (bbox.bottom + y < 0) y = 0 - bbox.bottom;
      if (bbox.top + y > modelHeight) y = modelHeight - bbox.top;

      if (model.isStopped()) {
        setMoleculePosition(atom.idx, x, y);
      } else {
        model.liveDrag(x, y);
      }
    }

    function setAtomPosition(i, xpos, ypos) {
      return model.setAtomProperties(i, {x: xpos, y: ypos});
    }

    function setMoleculePosition(i, xpos, ypos) {
      // The last argument ensures that the whole molecule will be moved.
      return model.setAtomProperties(i, {x: xpos, y: ypos}, false, true);
    }

    function isPositionValid(atomIdx) {
      // To validate position it's enough to set properties (even empty set) and make sure that "checkLocation"
      // and "moveMolecule" arguments are set to true.
      return model.setAtomProperties(atomIdx, {}, true, true);
    }

    function rotateMolecule(molecule, cx, cy, angle) {
      if (angle === 0) return;

      var rotationAllowed = true;
      for (var i = 0, len = molecule.length; i < len; i++) {
        var idx = molecule[i];
        var atom = model.getAtomProperties(idx);
        var newCoords = rotate(atom.x - cx, atom.y - cy, angle);
        var posAllowed = setAtomPosition(idx, newCoords.x + cx, newCoords.y + cy);
        rotationAllowed = rotationAllowed && posAllowed;
      }
      return rotationAllowed;
    }

    api = {
      bindModel: function (newModel) {
        model = newModel;

        atoms = model.getAtoms();
        modelWidth = model.get("width");
        modelHeight = model.get("height");

        // .register method destroys old menu instances first, so it's safe to call it each time we bind a new model.
        amniacidContextMenu.register(model, modelView, ".atoms-interaction-layer", function () {
          return contextMenuAtom;
        });
      }
    };

    init();

    return api;
  };
});

/*global define: false, d3: false */
/*jshint multistr: true */

define('models/md2d/views/atoms-renderer',['require','pixi','canvg','mustache','common/views/color','models/md2d/views/atoms-interactions','common/layout/detect-font-change'],function(require) {
  // Dependencies.
  var PIXI      = require('pixi'),
      canvg     = require('canvg'),
      mustache  = require('mustache'),
      d3rgba    = require('common/views/color').d3rgba,
      AtomsInteractions = require('models/md2d/views/atoms-interactions'),
      detectFontChange  = require('common/layout/detect-font-change'),

      FONT_WEIGHT  = 'bold',
      // font-family needs to be unescaped to support fonts wrapped in '' (e.g. 'Comic Sans MS').
      ATOM_SVG_TPL =
      '<svg x="0px" y="0px" width="{{ width }}px" height="{{ height }}px" \
       viewBox="0 0 32 32" xml:space="preserve"> \
        <style type="text/css"> \
        <![CDATA[ \
          text { \
            font-family: {{{ fontFamily }}}; \
            font-size: {{ fontSize }}px; \
            font-weight: ' + FONT_WEIGHT + '; \
            fill: #222; \
          } \
          .shadow { \
            stroke: rgba(255, 255, 255, 0.7); \
            stroke-width: 3px; \
          } \
        ]]> \
        </style> \
         <defs> \
            <radialGradient id="grad" cx="50%" cy="47%" r="53%" fx="35%" fy="30%"> \
              <stop stop-color="{{ lightCol }}" offset="0%"></stop> \
              <stop stop-color="{{ medCol }}" offset="40%"></stop> \
              <stop stop-color="{{ darkCol }}" offset="80%"></stop> \
              <stop stop-color="{{ medCol }}" offset="100%"></stop> \
            </radialGradient> \
         </defs> \
         <g opacity="{{ opacity }}"> \
           {{#excited}} \
            <circle fill="#ffe600" cx="16" cy="16" r="12"/> \
            <circle fill="url(#grad)" cx="16" cy="16" r="8"/> \
           {{/excited}} \
           {{^excited}} \
            <circle fill="url(#grad)" cx="16" cy="16" r="16"/> \
           {{/excited}} \
           <text class="shadow" text-anchor="middle" x="16" y="16" dy="0.31em">{{ label }}</text> \
           <text text-anchor="middle" x="16" y="16" dy="0.31em">{{ label }}</text> \
         </g> \
       </svg>',

      KE_SHADING_MIN_COLORS = ["#FFFFFF", "#F2F2F2", "#A4A4A4"],
      KE_SHADING_MAX_COLORS = ["#FFFFFF", "#FF8080", "#FF2020"],

      // Scales used for Charge Shading gradients.
      CHARGE_SHADING_STEPS = 25,
      NEUTRAL_COLORS = ["#FFFFFF", "#f2f2f2", "#A4A4A4"],
      chargeBlueLightColor = d3.scale.linear()
        .interpolate(d3.interpolateRgb)
        .range(["#FFFFFF", "#ffefff"]),
      chargeBlueMedColor = d3.scale.linear()
        .interpolate(d3.interpolateRgb)
        .range(["#f2f2f2", "#9090FF"]),
      chargeBlueDarkColor = d3.scale.linear()
        .interpolate(d3.interpolateRgb)
        .range(["#A4A4A4", "#3030FF"]),
      chargeRedLightColor = d3.scale.linear()
        .interpolate(d3.interpolateRgb)
        .range(["#FFFFFF", "#dfffff"]),
      chargeRedMedColor = d3.scale.linear()
        .interpolate(d3.interpolateRgb)
        .range(["#f2f2f2", "#FF8080"]),
      chargeRedDarkColor = d3.scale.linear()
        .interpolate(d3.interpolateRgb)
        .range(["#A4A4A4", "#FF2020"]),

      getChargeShadingColors = function (charge, style) {
        var chargeIndex = Math.round(Math.min(Math.abs(charge) / 3, 1) * CHARGE_SHADING_STEPS);
        chargeIndex /= CHARGE_SHADING_STEPS;
        if (style === 'biology' && charge > 0 || style === 'chemistry' && charge < 0) {
          return [chargeBlueLightColor(chargeIndex), chargeBlueMedColor(chargeIndex), chargeBlueDarkColor(chargeIndex)];
        } else if (style === 'biology' && charge < 0 || style === 'chemistry' && charge > 0) {
          return [chargeRedLightColor(chargeIndex), chargeRedMedColor(chargeIndex), chargeRedDarkColor(chargeIndex)];
        }
        return NEUTRAL_COLORS;
      },

      getHydrophobicityColors = function (h) {
        return h > 0 ?  ["#F0E6D1", "#E0A21B", "#AD7F1C"] : ["#dfffef", "#75a643", "#2a7216"];
      },

      RENDERING_OPTIONS = ["keShading", "chargeShading", "chargeShadingStyle", "atomNumbers", "showChargeSymbols",
                           "atomRadiusScale", "aminoAcidColorScheme", "aminoAcidLabels", "useThreeLetterCode", "viewPortZoom"];

  return function AtomsRenderer(modelView, model, pixiContainer, canvas) {
    // Public API object to be returned.
    var api,

        container,

        m2px,
        m2pxInv,

        modelAtoms,
        viewAtoms,

        elementTex = {},

        modelWidth,
        modelHeight,

        // Rendering options:
        renderMode = {},

        interactions = new AtomsInteractions(modelView, model, canvas);

    function init() {
      modelWidth = model.get("width");
      modelHeight = model.get("height");
      readRenderingOptions();
      // Modes require .setup() call:
      model.addPropertiesListener(RENDERING_OPTIONS, function () {
        readRenderingOptions();
        api.setup();
        // TODO: we shouldn't call .renderCanvas() here. E.g. when keShading is toggled, rendering
        // will be triggered both by bonds renderer and atoms renderer.
        modelView.renderCanvas();
      });
    }

    function readRenderingOptions() {
      RENDERING_OPTIONS.forEach(function (name) {
        renderMode[name] = model.get(name);
      });
    }

    function getAtomColors(i) {
      var atom = modelAtoms[i],
          elID = atom.element,
          props = model.getElementProperties(elID),
          colorStr, color;

      if (atom.marked) {
        colorStr = model.get("markColor");
        color = d3.rgb(colorStr);
        return [color.brighter(1).toString(), color.toString(), color.darker(1).toString()];
      }

      if (atom.aminoAcid) {
        switch(renderMode.aminoAcidColorScheme) {
          case "charge":
            return getChargeShadingColors(atom.charge, renderMode.chargeShadingStyle);
          case "hydrophobicity":
            return getHydrophobicityColors(atom.hydrophobicity);
          case "chargeAndHydro":
            if (atom.charge !== 0) {
              return getChargeShadingColors(atom.charge, renderMode.chargeShadingStyle);
            }
            return getHydrophobicityColors(atom.hydrophobicity);
          // case "none":
          // Do nothing, default rendering will be used.
        }
      }

      if (renderMode.keShading) {
        return KE_SHADING_MIN_COLORS;
      } else if (renderMode.chargeShading) {
        return getChargeShadingColors(atom.charge, renderMode.chargeShadingStyle);
      } else {
        if (typeof props.color === "number") {
          // Weird conversion, as we use color values literally imported from Classic MW. Perhaps we
          // should do that in MML -> JSON converter.
          colorStr = (props.color + Math.pow(2, 24)).toString(16);
          colorStr = "000000".substr(0, 6 - colorStr.length) + colorStr;
          color = d3.rgb("#" + colorStr);
        } else {
          // Use custom helper, as d3.rgb cannot parse rgba strings.
          color = d3rgba(props.color).rgb; // d3.rgb
        }
        return [color.brighter(1).toString(), color.toString(), color.darker(1).toString()];
      }
    }

    function getAtomOpacity(i) {
      var atom = modelAtoms[i],
          elID = atom.element,
          props = model.getElementProperties(elID);
      if (atom.marked || atom.aminoAcid || renderMode.keShading || renderMode.chargeShading) {
        // Special render modes, ignore atom's color.
        return atom.visible;
      }
      if (typeof props.color === "number") {
        // Colors specified in Classic MW format don't support alpha channel.
        return atom.visible;
      }
      // Color defined in HTML format can define alpha channel too (e.g. rgba(...)).
      return d3rgba(props.color).a * atom.visible;
    }

    function getAtomTexture(i, colors) {
      var elID = modelAtoms[i].element,
          radius = m2px(model.getElementProperties(elID).radius) * renderMode.atomRadiusScale,
          excitation = modelAtoms[i].excitation,
          label = getAtomLabel(i),
          opacity = getAtomOpacity(i),
          key;

      colors = colors || getAtomColors(i);
      key = opacity > 0 ? (elID + "-" + radius + "-" + colors.join("") + "-" + opacity + "-" + label.text + "-" + excitation + "-" + label.fontSize) :
                          (radius + "-invisible");

      if (elementTex[key] === undefined) {
        var canv = document.createElement("canvas"),
            tplData;

        watchFont(modelView.fontFamily);

        tplData = {
          width: excitation ? 4 * radius : 2 * radius,
          height: excitation ? 4 * radius : 2 * radius,
          lightCol: colors[0],
          medCol: colors[1],
          darkCol: colors[2],
          opacity: opacity,
          label: label.text,
          fontSize: label.fontSize,
          fontFamily: modelView.fontFamily,
          excited: excitation
        };

        canvg(canv, mustache.render(ATOM_SVG_TPL, tplData));

        // WebGL complains if the texture size is < 2px x 2px
        if (canv.width < 2) canv.width = 2;
        if (canv.height < 2) canv.height = 2;

        elementTex[key] = new PIXI.Texture.fromCanvas(canv);
        // canvg starts a timer, we don't need or want that timer
        canv.svg.stop();
      }
      return elementTex[key];
    }

    // TODO rename?
    function clearTextureCacheAndRedraw() {
      elementTex = {};
      viewAtoms.forEach(function(atom, i) {
        atom.setTexture(getAtomTexture(i));
      });
      modelView.renderCanvas();
    }

    function watchFont(font) {
      detectFontChange({
        font: font,
        weight: FONT_WEIGHT, // so it matches weight used in ATOM_SVG_TPL
        onchange: clearTextureCacheAndRedraw
      });
    }

    function getAtomLabel(i) {
      var textVal = "",
          sizeRatio = 0;

      if (renderMode.atomNumbers) {
        textVal = i;
        sizeRatio = 1.2;
      } else if (renderMode.aminoAcidLabels && renderMode.useThreeLetterCode && modelAtoms[i].label) {
        textVal = modelAtoms[i].label;
        sizeRatio = 1;
      } else if (renderMode.aminoAcidLabels && !renderMode.useThreeLetterCode && modelAtoms[i].symbol) {
        textVal = modelAtoms[i].symbol;
        sizeRatio = 1.4;
      } else if (renderMode.showChargeSymbols) {
        if (modelAtoms[i].charge > 0) {
          textVal = "+";
        } else if (modelAtoms[i].charge < 0) {
          textVal = "-";
        }
        sizeRatio = 1.6;
      }

      return {
        text: textVal,
        fontSize: sizeRatio * 16 // In fact: sizeRatio * atom radius. The value 16 is based on the
                                 // current SVG viewBox and <circle> "r" property.
      };
    }

    api = {
      setup: function () {
        var i, len, atom, keSprite;

        if (container) {
          pixiContainer.removeChild(container);
        }
        container = new PIXI.DisplayObjectContainer();
        pixiContainer.addChild(container);

        m2px = modelView.model2canvas;
        m2pxInv = modelView.model2canvasInv;

        viewAtoms = [];
        modelAtoms = model.getAtoms();

        for (i = 0, len = modelAtoms.length; i < len; ++i) {
          atom = new PIXI.Sprite(getAtomTexture(i));
          atom.anchor.x = 0.5;
          atom.anchor.y = 0.5;
          atom.i = i;
          atom.marked = modelAtoms[i].marked;
          viewAtoms.push(atom);
          container.addChild(atom);
        }

        if (renderMode.keShading) {
          for (i = 0, len = modelAtoms.length; i < len; ++i) {
            keSprite = new PIXI.Sprite(getAtomTexture(i, KE_SHADING_MAX_COLORS));
            keSprite.anchor.x = 0.5;
            keSprite.anchor.y = 0.5;
            viewAtoms[i].keSprite = keSprite;
            container.addChild(keSprite);
          }
        }

        api.update();
      },

      bindModel: function (newModel) {
        model = newModel;
        init();

        interactions.bindModel(newModel);
      },

      update: function () {
        var i, len, viewAtom, modelAtom, x, y, minKE, maxKE;

        for (i = 0, len = viewAtoms.length; i < len; ++i) {
          viewAtom = viewAtoms[i];
          modelAtom = modelAtoms[i];
          x = m2px(modelAtom.x);
          y = m2pxInv(modelAtom.y);

          viewAtom.position.x = x;
          viewAtom.position.y = y;

          if (modelAtom.marked !== viewAtom.marked) {
            // Make sure that marked state is always reflected by the view.
            viewAtom.setTexture(getAtomTexture(i));
            viewAtom.marked = modelAtoms[i].marked;
          }

          if (renderMode.keShading) {
            minKE = model.get('keShadingMinEnergy');
            maxKE = model.get('keShadingMaxEnergy');
            viewAtom.keSprite.alpha = Math.min((model.getAtomKineticEnergy(i) - minKE) / (maxKE - minKE), 1);
            viewAtom.keSprite.position.x = x;
            viewAtom.keSprite.position.y = y;
          }

          if (model.properties.useQuantumDynamics) {
            viewAtom.setTexture(getAtomTexture(i));
          }
        }
      },

      getAtomColors: getAtomColors,

      getAtomOpacity: getAtomOpacity
    };

    init();

    return api;
  };
});

/*global define: false */

define('models/md2d/views/bonds-renderer',['require','pixi'],function(require) {
  // Dependencies.
  var PIXI = require('pixi'),

      RADIAL_BOND_TYPES = {
        STANDARD_STICK: 101,
        LONG_SPRING: 102,
        BOND_SOLID_LINE: 103,
        GHOST: 104,
        UNICOLOR_STICK: 105,
        SHORT_SPRING: 106,
        DOUBLE_BOND: 107,
        TRIPLE_BOND: 108,
        DISULPHIDE_BOND: 109
      },

      RENDERING_OPTIONS = ["keShading", "chargeShading", "chargeShadingStyle", "aminoAcidColorScheme"];

  return function BondsRenderer(modelView, model, pixiContainer, atomsRenderer) {
    // Public API object to be returned.
    var api,

        m2px,
        m2pxInv,

        modelBonds,
        modelAtoms,

        graphics = new PIXI.Graphics();

    function init() {
      model.addPropertiesListener(RENDERING_OPTIONS, function () {
        api.setup();
        // TODO: we shouldn't call .renderCanvas() here. E.g. when keShading is toggled, rendering
        // will be triggered both by bonds renderer and atoms renderer.
        modelView.renderCanvas();
      });
    }

    function renderSpring(d) {
      var x1 = m2px(d.x1),
          y1 = m2pxInv(d.y1),
          x2 = m2px(d.x2),
          y2 = m2pxInv(d.y2),
          dx = x2 - x1,
          dy = y2 - y1,

          length = Math.sqrt(dx * dx + dy * dy) / m2px(0.01),

          numTurns = Math.floor(d.length * 24),
          springDiameter = length / numTurns,

          costheta = dx / length,
          sintheta = dy / length,
          cosThetaDiameter = costheta * springDiameter,
          sinThetaDiameter = sintheta * springDiameter,
          cosThetaSpikes = costheta * numTurns,
          sinThetaSpikes = sintheta * numTurns,
          pointX, pointY, i;

      graphics.lineStyle(getBondWidth(d), getBondColor(d, 1), getBondOpacity(d, 1));
      graphics.moveTo(x1, y1);
      for (i = 0; i < numTurns; i++) {
        if (i % 2 === 0) {
          pointX = x1 + (i + 0.5) * cosThetaDiameter - 0.5 * sinThetaSpikes;
          pointY = y1 + (i + 0.5) * sinThetaDiameter + 0.5 * cosThetaSpikes;
        } else {
          pointX = x1 + (i + 0.5) * cosThetaDiameter + 0.5 * sinThetaSpikes;
          pointY = y1 + (i + 0.5) * sinThetaDiameter - 0.5 * cosThetaSpikes;
        }
        graphics.lineTo(pointX, pointY);
      }
      graphics.lineTo(x2, y2);
    }

    function renderBond(d) {
      var x1 = m2px(d.x1),
          y1 = m2pxInv(d.y1),
          x2 = m2px(d.x2),
          y2 = m2pxInv(d.y2),
          r1 = m2px(modelAtoms[d.atom1].radius),
          r2 = m2px(modelAtoms[d.atom2].radius),
          dx = x2 - x1,
          dy = y2 - y1,
          len = Math.sqrt(dx * dx + dy * dy);

      // Fast path if bond is invisible anyway. Use 2 ratio, because when length is exactly equal
      // to r1 and r2 sum, double and triple bonds can be still visible (they are wide enough).
      if (2 * len - r1 - r2 <= 0) return;

      var midRatio = 0.5 * (len + r1 - r2) / len,
          xMid = x1 + midRatio * dx,
          yMid = y1 + midRatio * dy,

          bondWidth = getBondWidth(d),
          bondColor1 = getBondColor(d, 1),
          bondColor2 = getBondColor(d, 2),
          bondOpacity1 = getBondOpacity(d, 1),
          bondOpacity2 = getBondOpacity(d, 2),
          bondShift, bondAngle, xs, ys;

      if (d.type === RADIAL_BOND_TYPES.DOUBLE_BOND) {
        bondShift = m2px(Math.min(modelAtoms[d.atom1].radius, modelAtoms[d.atom2].radius)) * 0.4;
        bondAngle = Math.atan2(dy, dx);
        xs = Math.sin(bondAngle) * bondShift;
        ys = -Math.cos(bondAngle) * bondShift;

        graphics.lineStyle(bondWidth, bondColor1, bondOpacity1);
        graphics.moveTo(x1 + xs, y1 + ys);
        graphics.lineTo(xMid + xs, yMid + ys);
        graphics.moveTo(x1 - xs, y1 - ys);
        graphics.lineTo(xMid - xs, yMid - ys);

        graphics.lineStyle(bondWidth, bondColor2, bondOpacity2);
        graphics.moveTo(xMid + xs, yMid + ys);
        graphics.lineTo(x2 + xs, y2 + ys);
        graphics.moveTo(xMid - xs, yMid - ys);
        graphics.lineTo(x2 - xs, y2 - ys);
      } else if (d.type === RADIAL_BOND_TYPES.TRIPLE_BOND) {
        bondShift = m2px(Math.min(modelAtoms[d.atom1].radius, modelAtoms[d.atom2].radius)) * 0.52;
        bondAngle = Math.atan2(dy, dx);
        xs = Math.sin(bondAngle) * bondShift;
        ys = -Math.cos(bondAngle) * bondShift;

        graphics.lineStyle(bondWidth, bondColor1, bondOpacity1);
        graphics.moveTo(x1, y1);
        graphics.lineTo(xMid, yMid);
        graphics.moveTo(x1 + xs, y1 + ys);
        graphics.lineTo(xMid + xs, yMid + ys);
        graphics.moveTo(x1 - xs, y1 - ys);
        graphics.lineTo(xMid - xs, yMid - ys);

        graphics.lineStyle(bondWidth, bondColor2, bondOpacity2);
        graphics.moveTo(xMid, yMid);
        graphics.lineTo(x2, y2);
        graphics.moveTo(xMid + xs, yMid + ys);
        graphics.lineTo(x2 + xs, y2 + ys);
        graphics.moveTo(xMid - xs, yMid - ys);
        graphics.lineTo(x2 - xs, y2 - ys);
      } else if (d.type !== RADIAL_BOND_TYPES.GHOST) {
        // STANDARD_STICK and other types that are not yet supported.
        // However, GHOST bonds will not be drawn.
        graphics.lineStyle(bondWidth, bondColor1, bondOpacity1);
        graphics.moveTo(x1, y1);
        graphics.lineTo(xMid, yMid);

        graphics.lineStyle(bondWidth, bondColor2, bondOpacity2);
        graphics.moveTo(xMid, yMid);
        graphics.lineTo(x2, y2);
      }
    }

    function getBondColor(d, num) {
      if (d.type === RADIAL_BOND_TYPES.SHORT_SPRING) {
        return 0x888888;
      } else if (d.type === RADIAL_BOND_TYPES.DISULPHIDE_BOND) {
        return 0xffe95a;
      } else if (num === 1) {
        return parseInt(atomsRenderer.getAtomColors(d.atom1)[2].substr(1), 16);
      } else if (num === 2) {
        return parseInt(atomsRenderer.getAtomColors(d.atom2)[2].substr(1), 16);
      }
    }

    function getBondOpacity(d, num) {
      if (num === 1) {
        return atomsRenderer.getAtomOpacity(d.atom1);
      } else if (num === 2) {
        return atomsRenderer.getAtomOpacity(d.atom2);
      }
    }

    function getBondWidth(bond) {
      if (bond.type === RADIAL_BOND_TYPES.SHORT_SPRING) {
        return m2px(0.012);
        // The following code is intended to use a thicker stroke-width when
        // the spring constant is larger ... but to work properly in models with
        // both MD2D and MKS units schemes the model would need to supply
        // an appropriately scaled default spring constant.
        // For example in the Spring and Mass Interactive which uses an MKS unit
        // scheme the spring constant is varied between 0.001 and 0.003 ... while in
        // the Comparing Dipole atom-pulling Interactive that uses an MD2D unit
        // scheme the spring constant is 10.
        // return (1 + Math.log(1+bond.strength*1000)) * 0.25;;
      }
      var result = m2px(Math.min(modelAtoms[bond.atom1].radius, modelAtoms[bond.atom2].radius));
      if (bond.type === RADIAL_BOND_TYPES.DOUBLE_BOND) {
        return result * 0.50;
      } else if (bond.type === RADIAL_BOND_TYPES.TRIPLE_BOND) {
        return result * 0.35;
      } else { // STANDARD_STICK and other types that are not yet implemented.
        return result * 0.75;
      }
    }

    api = {
      setup: function () {
        modelBonds = model.getRadialBonds();
        modelAtoms = model.getAtoms();

        m2px = modelView.model2canvas;
        m2pxInv = modelView.model2canvasInv;

        api.update();
      },

      bindModel: function (newModel) {
        model = newModel;

        init();
      },

      update: function () {
        var i, len, bond;

        graphics.clear();
        for (i = 0, len = modelBonds.length; i < len; ++i) {
          bond = modelBonds[i];
          if (bond.type === RADIAL_BOND_TYPES.SHORT_SPRING) {
            renderSpring(bond);
          } else {
            renderBond(bond);
          }
        }
      }
    };

    init();
    pixiContainer.addChild(graphics);

    return api;
  };
});

/*global define: false */

define('models/md2d/views/vectors-renderer',['require','pixi'],function(require) {
  // Dependencies.
  var PIXI = require('pixi');

  return function VectorsRenderer(pixiContainer, config) {
    // Public API object to be returned.
    var api,

        m2px,
        m2pxInv,

        container,
        viewVectors,

        // Vectors rendering enabled or disabled.
        show,
        // Number of vectors to render.
        count,
        // Physical vector properties (functions!).
        xFunc, yFunc, vxFunc, vyFunc,
        // Visual vector properties.
        alphaFunc, length, width, color, dirOnly;

    function readOptions() {
      count = config.count;

      xFunc = config.x;
      yFunc = config.y;
      vxFunc = config.vx;
      vyFunc = config.vy;
      alphaFunc = config.alpha;

      show = config.show;
      length = config.length;
      width = config.width;
      color = config.color;
      dirOnly = config.dirOnly;

      m2px = config.m2px;
      m2pxInv = config.m2pxInv;
    }

    function getVectorTexture() {
      var canv = document.createElement("canvas"),
          ctx = canv.getContext("2d");
      canv.width = 1;
      canv.height = 1;
      ctx.fillStyle = color;
      ctx.fillRect(0, 0, 1, 1);

      return new PIXI.Texture.fromCanvas(canv);
    }

    function getVectorArrowheadTexture() {
      var canv = document.createElement("canvas"),
          ctx = canv.getContext("2d"),
          dim = m2px(3.5 * width);
      canv.width = dim;
      canv.height = dim;
      ctx.fillStyle = color;

      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.lineTo(dim, 0);
      ctx.lineTo(dim * 0.5, dim);
      ctx.closePath();
      ctx.fill();

      return new PIXI.Texture.fromCanvas(canv);
    }

    function renderVector(i) {
      var vec = viewVectors[i],
          x = xFunc(i),
          y = yFunc(i),
          vx = vxFunc(i) * length,
          vy = vyFunc(i) * length,
          len = Math.sqrt(vx * vx + vy * vy),
          rot = Math.PI + Math.atan2(vx, vy),
          arrowHead = vec.arrowHead;
      if (dirOnly) {
        // 0.15 is a bit random, empirically set value to match previous rendering done by SVG.
        vx = 0.15 * length * vx / len;
        vy = 0.15 * length * vy / len;
        len = 0.15 * length;
      }
      var lenInPx = m2px(len);
      if (lenInPx < 1) {
        // Hide completely tiny vectors (< 1px).
        vec.alpha = 0;
        arrowHead.alpha = 0;
        return;
      } else if (alphaFunc) {
        vec.alpha = alphaFunc(i);
        arrowHead.alpha = alphaFunc(i);
      } else {
        vec.alpha = 1;
        arrowHead.alpha = 1;
      }
      if (lenInPx > 1e6) {
        // When vectors has enormous size, it can cause rendering artifacts. Limit it.
        var s = lenInPx / 1e6;
        vx /= s; vy /= s; len /= s; lenInPx /= s;
      }
      // Vector.
      vec.position.x = m2px(x);
      vec.position.y = m2pxInv(y);
      vec.scale.y = lenInPx;
      vec.rotation = rot;
      // Arrowhead.
      arrowHead.position.x = m2px(x + vx);
      arrowHead.position.y = m2pxInv(y + vy);
      arrowHead.rotation = rot;
    }


    api = {
      setup: function () {
        readOptions();

        if (container) {
          pixiContainer.removeChild(container);
          container = null;
        }
        if (!show || count === 0) return;

        container = new PIXI.DisplayObjectContainer();
        pixiContainer.addChild(container);

        var i, vec, arrowHead, tex;

        viewVectors = [];

        tex = getVectorTexture();
        for (i = 0; i < count; ++i) {
          vec = new PIXI.Sprite(tex);
          vec.anchor.x = 0.5;
          vec.scale.x = m2px(width);
          vec.i = i;
          viewVectors.push(vec);
          container.addChild(vec);
        }
        tex = getVectorArrowheadTexture();
        for (i = 0; i < count; ++i) {
          arrowHead = new PIXI.Sprite(tex);
          arrowHead.anchor.x = 0.5;
          viewVectors[i].arrowHead = arrowHead;
          container.addChild(arrowHead);
        }

        api.update();
      },

      update: function () {
        if (!show || count === 0) return;
        for (var i = 0; i < count; ++i) {
          renderVector(i);
        }
      }
    };

    return api;
  };
});

/*global define: false */

define('models/md2d/views/field-renderer',['require','pixi'],function(require) {
  // Dependencies.
  var PIXI = require('pixi');

  function logistic(x) {
    return 1 / (1 + Math.exp(-x));
  }

  return function FieldRenderer(pixiContainer, config) {

    var api,

        container,
        sprites,

        m2px,
        m2pxInv,

        // Rendering enabled or disabled?
        show,

        // Number of needles to render.
        count,

        // Functions that return x, y, vx, vy of field for grid position i
        xFunc, yFunc, vxFunc, vyFunc,

        // Function that returns alpha value to use for grid position i
        alphaFunc,

        // Needle length in nm
        length,

        // Need color, less alpha
        color;

    function readOptions() {
      count = config.count;

      xFunc = config.x;
      yFunc = config.y;
      vxFunc = config.vx;
      vyFunc = config.vy;
      alphaFunc = config.alpha;

      show = config.show;
      length = config.length;
      color = config.color;

      m2px = config.m2px;
      m2pxInv = config.m2pxInv;
    }

    function getTexture() {
      var canv = document.createElement("canvas");
      var ctx = canv.getContext("2d");

      // Smoothly range the needle's aspect ratio from 5:1 when width is ~60px to 3:1 when width is
      // ~30px. Needle doesn't look good "fat" when long, and shouldn't be too narrow when short.
      var MIN_FRACTION = 1/5;
      var MAX_FRACTION = 1/3;
      var LOW_LENGTH_THRESHOLD  = 30;
      var HIGH_LENGTH_THRESHOLD = 60;

      var l = m2px(length);
      var x = (l - LOW_LENGTH_THRESHOLD) / (HIGH_LENGTH_THRESHOLD - LOW_LENGTH_THRESHOLD);
      var fraction = MIN_FRACTION + logistic(-8 * (x - 0.5)) * (MAX_FRACTION - MIN_FRACTION);
      var w = l * fraction;

      canv.height = l;
      canv.width = w;

      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.lineTo(w * 0.5, l);
      ctx.lineTo(w, 0);
      ctx.fillStyle = color;
      ctx.fill();

      return new PIXI.Texture.fromCanvas(canv);
    }

    function renderSprite(i) {
      var sprite = sprites[i];
      var x = xFunc(i);
      var y = yFunc(i);

      sprite.position.x = m2px(x);
      sprite.position.y = m2pxInv(y);
      sprite.rotation = Math.PI + Math.atan2(vxFunc(i), vyFunc(i));
      sprite.alpha = alphaFunc(i);
    }


    api = {
      setup: function() {
        var i, sprite, texture;

        readOptions();

        if (container) {
          pixiContainer.removeChild(container);
          container = null;
        }
        if (!show || count === 0) return;

        container = new PIXI.DisplayObjectContainer();
        pixiContainer.addChild(container);

        sprites = [];

        texture = getTexture();
        for (i = 0; i < count; ++i) {
          sprite = new PIXI.Sprite(texture);
          // Should pivot on center
          sprite.anchor.x = 0.5;
          sprite.anchor.y = 0.5;
          sprite.i = i;
          sprites.push(sprite);
          container.addChild(sprite);
        }

        api.update();
      },

      update: function () {
        if (!show || count === 0) return;
        for (var i = 0; i < count; ++i) {
          renderSprite(i);
        }
      }
    };

    return api;
  };
});

define('models/md2d/views/vdw-lines-renderer',['require','pixi'],function(require) {
  'use strict';
  var PIXI = require('pixi');

  var NUMBER_OF_SEGMENTS = 5;

  return function VdwLinesRenderer(modelView, model, pixiContainer) {

    // Pixi container where our sprites go.
    var container;

    // Pixi texture of a VdW Line
    var texture;

    // Width of VdW lines in pixels
    var strokeWidth;

    // Length of VdW line texture in pixels
    var texLength;

    // Pixi sprites for each line
    var sprites = [];

    /**
      Return a PIXI.Texture from which the VdW sprites will be drawn. We want the height of this
      texture to be the actual on-canvas width of a VdW line in pixels, as represented by
      'strokeWidth', so that the boundaries between the line's segments and gaps are sharp. (Leaving
      the height at say 1px and scaling sprites up to the correct linewidth would likely introduce
      blurred edges).
    */
    function getLineTexture(strokeWidth) {
      var canvas = document.createElement('canvas');
      var ctx = canvas.getContext('2d');

      var gapLength = modelView.model2canvas(0.02);
      var segmentLength = modelView.model2canvas(0.03);
      var halfStroke = strokeWidth / 2;

      texLength = NUMBER_OF_SEGMENTS * (segmentLength + gapLength) + segmentLength;

      canvas.width = texLength;
      canvas.height = strokeWidth;
      ctx.strokeStyle = "#aaa";
      ctx.lineWidth = strokeWidth;

      var x = 0;
      for (var i = 0; i < NUMBER_OF_SEGMENTS; i++) {
        ctx.moveTo(x, halfStroke);
        ctx.lineTo(x + segmentLength, halfStroke);
        ctx.stroke();
        x += segmentLength + gapLength;
      }

      return PIXI.Texture.fromCanvas(canvas);
    }

    function removeContainerAndSprites() {
      if (container) {
        pixiContainer.removeChild(container);
        container = null;
      }
      sprites = [];
    }

    function createContainerAndSprites() {
      container = new PIXI.DisplayObjectContainer();
      pixiContainer.addChild(container);
      sprites = [];
    }

    /**
      Call before calling update() for the first time and after any change to the model or canvas
      dimensions; resets the display container and texture, then calls update().
    */
    function setup() {
      var newStrokeWidth;

      if ( ! model.properties.showVDWLines ) {
        removeContainerAndSprites();
        return;
      }

      // Some interactives' scripts call repaint a lot, which calls this method. However, if there
      // is a container and the model dimensions have not changed, there is no setup to do. The
      // update method will ensure that the correct VdW lines are added or removed.
      newStrokeWidth = modelView.model2canvas(0.02);
      if (newStrokeWidth !== strokeWidth) {
        strokeWidth = newStrokeWidth;

        texture = getLineTexture(strokeWidth);
        removeContainerAndSprites();
        createContainerAndSprites();
      }

      if ( ! container ) {
        createContainerAndSprites();
      }

      update();
    }

    /**
      Call whenever the VdW lines need to be repainted; however, be sure to call setup() before the
      first time this is called and thereafter whenever a scale change is made.

      Note that, because VdW lines by their nature need to be added or removed at each tick, this
      method adds or removes VdW lines as necessary. (Many of the other renderers require that setup
      be called when the number of items to draw is changed.)
    */
    function update() {

      if ( ! model.properties.showVDWLines ) {
        return;
      }

      var vdwPairs = model.get_vdw_pairs();
      var atoms = model.getAtoms();
      var i;
      var atom1, atom2;
      var dx, dy;
      var x1, y1, x2, y2;

      for (i = sprites.length; i < vdwPairs.count; i++) {
        sprites[i] = new PIXI.Sprite(texture);
        sprites[i].anchor.y = 0.5;
        container.addChild(sprites[i]);
      }

      for (i = 0; i < vdwPairs.count; i++) {
        atom1 = atoms[vdwPairs.atom1[i]];
        atom2 = atoms[vdwPairs.atom2[i]];

        x1 = modelView.model2canvas(atom1.x);
        y1 = modelView.model2canvasInv(atom1.y);
        x2 = modelView.model2canvas(atom2.x);
        y2 = modelView.model2canvasInv(atom2.y);

        dx = x2 - x1;
        dy = y2 - y1;

        sprites[i].visible = true;
        // stretches/shrinks the sprite to the desired length; appears to be just fine visually.
        sprites[i].scale.x = Math.ceil(Math.sqrt(dx * dx + dy * dy)) / texLength;
        // Make sure the midpoint of the left edge is at (x1, y1); sprite.position.{x|t} refer to
        // the upper left corner instead. A little bit of math is required because Pixi appears to
        // apply the rotation before the translation.
        sprites[i].position.x = x1;
        sprites[i].position.y = y1;
        sprites[i].rotation = Math.atan2(dy, dx);
      }

      // hide unused sprites, but don't delete them -- VdW lines come and go on each tick!
      for (; i < sprites.length; i++) {
        sprites[i].visible = false;
      }
    }

    function bindModel(_model) {
      model = _model;
    }

    return {
      setup: setup,
      update: update,
      bindModel: bindModel
    };
  };
});

/*global define, d3 */

define('models/md2d/views/genetic-elements-renderer',['require','models/md2d/views/nucleotides','common/resources-url'],function (require) {
  var nucleotides = require('models/md2d/views/nucleotides'),
      resourcesUrl = require('common/resources-url'),

      SCALE = 0.007,
      W = {
        "CELLS": 720,
        "DNA1": 661,
        "DNA2": 720,
        "DNA3": 337.4,
        "POLY_UNDER": 426.15,
        "POLY_OVER": 402.525,
        "NUCLEUS": 729.45,
        "RIBO_TOP": 550.7,
        "RIBO_BOTTOM": 509.031,
        "RIBO_UNDER": 550.55,
        "RIBO_OVER": 550.7,
        "TRNA": 117.325,
        "TRNA_NECK": 15.925
      },
      H = {
        "CELLS": 500,
        "DNA1": 550,
        "DNA2": 414.263,
        "DNA3": 89.824,
        "POLY_UNDER": 368.6,
        "POLY_OVER": 368.6,
        "NUCLEUS": 543.199,
        "RIBO_TOP": 250,
        "RIBO_BOTTOM": 147.15,
        "RIBO_UNDER": 311.6,
        "RIBO_OVER": 311.6,
        "TRNA": 67.9,
        "TRNA_NECK": 21.14,
        "A": 31.15
      },

      OBJECT_NAMES = [
        "background",
        "cells", "dna1", "dna2", "dna3",
        "polymeraseUnder", "polymeraseOver",
        "polymeraseUnder", "polymeraseOver",
        "dna", "dnaComp", "mrna", "nucleus",
        "ribosomeBottom", "ribosomeTop",
        "ribosomeUnder", "ribosomeOver",
        "trna", "viewPort"
      ];

  (function () {
    var name;
    for (name in W) {
      if (W.hasOwnProperty(name)) {
        W[name] *= SCALE;
      }
    }
    for (name in H) {
      if (H.hasOwnProperty(name)) {
        H[name] *= SCALE;
      }
    }
  }());

  function getDefs(parent) {
    var defs = parent.select("defs");
    if (defs.empty()) {
      defs = parent.append("defs");
    }
    return defs;
  }

  function appendTranscriptionBg(parent) {
    var defs = getDefs(parent),
        gradient;

    if (defs.select("#transcription-bg").empty()) {
      gradient = defs.append("linearGradient")
        .attr("id", "transcription-bg")
        .attr("x1", "0%")
        .attr("y1", "0%")
        .attr("x2", "0%")
        .attr("y2", "100%");
      gradient.append("stop")
        .attr("stop-color", "#C8DD69")
        .attr("offset", "0%");
      gradient.append("stop")
        .attr("stop-color", "#778B3D")
        .attr("offset", "100%");
    }
  }

  function GeneticElementsRenderer(node, model2px, model2pxInv, model) {

    function scaleFunc(d) {
      return "scale(" + d.scale + ")";
    }
    function opacityFunc(d) {
      return d.opacity;
    }
    function translateFuncInv(d) {
      var x = d.translateX || 0,
          y = d.translateY || 0;
      return "translate(" + model2px(x) + " " + model2pxInv(y) + ")";
    }
    function translateScaleFuncInv(d) {
      return translateFuncInv(d) + " " + scaleFunc(d);
    }
    // Returns optimal (minimal) boundaries of the DNA or mRNA view array.
    // They are based on the current vieport position, size and knowledge
    // about single nucleotide size.
    function getOptBoundaries(dna) {
      var shift = dna ? model.geneticEngine().PRECODING_LEN : 0,
          lowIdx = Math.floor(model.properties.viewPortX / nucleotides.WIDTH) - 2,
          highIdx = Math.ceil((model.properties.viewPortX + model.properties.viewPortWidth) / nucleotides.WIDTH) + 4;
      return [Math.max(0, lowIdx + shift), highIdx + shift];
    }

    return {
      cells: function (parent, data) {
        var cells = parent.select(".background-layer").selectAll(".cells").data(data.cells);
        cells.enter().append("image").attr({
          "class": "cells",
          "x": model2px(W.CELLS * -0.567),
          "y": model2px(H.CELLS * -0.445),
          "width": model2px(W.CELLS),
          "height": model2px(H.CELLS),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/Cells.svg"),
          "transform": translateScaleFuncInv
        }).style("opacity", opacityFunc);
        d3.transition(cells)
          .attr("transform", translateScaleFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(cells.exit()).remove();
      },

      dna1: function (parent, data) {
        var dna1 = parent.select(".dna-layer").selectAll(".dna1").data(data.dna1);
        dna1.enter().append("image").attr({
          "class": "dna1",
          "x": model2px(W.DNA1 * -0.5),
          "y": model2px(H.DNA1 * -0.5),
          "width": model2px(W.DNA1),
          "height": model2px(H.DNA1),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/DNA_InsideNucleus_1.svg"),
          "transform": translateScaleFuncInv
        }).style("opacity", opacityFunc);
        d3.transition(dna1)
          .attr("transform", translateScaleFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(dna1.exit()).remove();
      },

      dna2: function (parent, data) {
        var dna2 = parent.select(".dna-layer").selectAll(".dna2").data(data.dna2);
        dna2.enter().append("image").attr({
          "class": "dna2",
          "x": model2px(W.DNA2 * -0.5),
          "y": model2px(H.DNA2 * -0.404),
          "width": model2px(W.DNA2),
          "height": model2px(H.DNA2),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/DNA_InsideNucleus_2.svg"),
          "transform": translateScaleFuncInv
        }).style("opacity", opacityFunc);
        d3.transition(dna2)
          .attr("transform", translateScaleFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(dna2.exit()).remove();
      },

      dna3: function (parent, data) {
        var dna3units = 14,
            dna3, dna3Enter;
        dna3 = parent.select(".dna-layer").selectAll(".dna3").data(data.dna3);
        dna3Enter = dna3.enter().append("g").attr({
          "class": "dna3 main-dna",
          "transform": translateScaleFuncInv
        }).style("opacity", opacityFunc);
        dna3Enter.selectAll("dna3-unit").data(new Array(dna3units)).enter().append("image").attr({
          "class": "dna3-unit",
          "x": function (d, i) { return (i - dna3units * 0.5) * model2px(W.DNA3) * 0.98; },
          "y": model2px(H.DNA3 * -0.5),
          "width": model2px(W.DNA3),
          "height": model2px(H.DNA3),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/DoubleHelix_Unit.svg")
        });
        d3.transition(dna3)
          .attr("transform", translateScaleFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(dna3.exit()).remove();
      },

      dna: function (parent, data, enterExitOnly) {
        var viewModel  = model.geneticEngine().viewModel,
            boundaries = getOptBoundaries(true),
            bonds      = data.dna[0] ? data.dna[0].bonds : 0,
            n          = nucleotides(),
            dna        = parent.select(".dna-layer").selectAll(".dna");

        // Configure nucleotides.
        n.model2px(model2px)
         .sequence(viewModel.DNA.slice(boundaries[0], boundaries[1]))
         .glow(model.get("DNAMutations"))
         .bonds(bonds);

        if (enterExitOnly) {
          dna.call(n.enterExitOnly(true));
          return;
        }

        dna = dna.data(data.dna);

        // DNA enter:
        dna.enter().append("g").attr({
          "class": "dna",
          "transform": translateFuncInv
        });
        // DNA update:
        dna.call(n);
        d3.transition(dna).attr("transform", translateFuncInv);
        // DNA exit:
        d3.transition(dna.exit()).remove();
      },

      dnaComp: function (parent, data, enterExitOnly) {
        var viewModel  = model.geneticEngine().viewModel,
            boundaries = getOptBoundaries(true),
            bonds      = data.dnaComp[0] ? data.dnaComp[0].bonds : 0,
            n          = nucleotides(),
            dnaComp    = parent.select(".dna-layer").selectAll(".dna-comp");

        // Configure nucleotides.
        n.model2px(model2px)
         .sequence(viewModel.DNAComp.slice(boundaries[0], boundaries[1]))
         .direction(2)
         .glow(model.get("DNAMutations"))
         .bonds(bonds);

        if (enterExitOnly) {
          dnaComp.call(n.enterExitOnly(true));
          return;
        }

        dnaComp = dnaComp.data(data.dnaComp);

        // DNA Comp enter:
        dnaComp.enter().append("g").attr({
          "class": "dna-comp",
          "transform": translateFuncInv
        });
        // DNA Comp update:
        dnaComp.call(n);
        d3.transition(dnaComp).attr("transform", translateFuncInv);
        // DNA Comp exit:
        d3.transition(dnaComp.exit()).remove();
      },

      mrna: function (parent, data, enterExitOnly) {
        var geneticEngine = model.geneticEngine(),
            mrnaSequence  = geneticEngine.viewModel.mRNA,
            boundaries    = getOptBoundaries(),
            stopCodons    = geneticEngine.stopCodonsHash(),
            bonds         = data.mrna[0] ? data.mrna[0].bonds : 0,
            dir           = data.mrna[0] ? data.mrna[0].direction : 1,
            n             = nucleotides(),
            mrna          = parent.select(".dna-layer").selectAll(".mrna");

        // Configure nucleotides.
        n.model2px(model2px)
         .sequence(mrnaSequence.slice(boundaries[0], boundaries[1]))
         .backbone("RNA")
         .direction(dir)
         .bonds(bonds)
         .stopCodonsHash(stopCodons);

        if (enterExitOnly) {
          mrna.call(n.enterExitOnly(true));
          return;
        }

        mrna = mrna.data(data.mrna);

        // mRNA enter:
        mrna.enter().append("g").attr({
          "class": "mrna",
          "transform": translateFuncInv
        });
        // mRNA update:
        mrna.call(n);
        d3.transition(mrna).attr("transform", translateFuncInv);
        // mRNA exit:
        d3.transition(mrna.exit()).remove();
      },

      polymeraseUnder: function (parent, data) {
        var polyUnder = parent.select(".under-dna-layer").selectAll(".polymerase-under").data(data.polymeraseUnder);
        polyUnder.enter().append("image").attr({
          "class": "polymerase-under",
          "x": model2px(W.POLY_UNDER * -0.5),
          "y": model2px(H.POLY_UNDER * -0.5),
          "width": model2px(W.POLY_UNDER),
          "height": model2px(H.POLY_UNDER),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/Polymerase_Under.svg"),
          "transform": translateScaleFuncInv
        }).style("opacity", opacityFunc);
        d3.transition(polyUnder)
          .attr("transform", translateScaleFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(polyUnder.exit()).remove();
      },

      polymeraseOver: function (parent, data) {
        var polyOver = parent.select(".over-dna-layer").selectAll(".polymerase-over").data(data.polymeraseOver);
        polyOver.enter().append("image").attr({
          "class": "polymerase-over",
          "x": model2px(W.POLY_OVER * -0.5),
          "y": model2px(H.POLY_OVER * -0.5),
          "width": model2px(W.POLY_OVER),
          "height": model2px(H.POLY_OVER),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/Polymerase_Over.svg"),
          "transform": translateScaleFuncInv
        }).style("opacity", opacityFunc);
        d3.transition(polyOver)
          .attr("transform", translateScaleFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(polyOver.exit()).remove();
      },

      nucleus: function (parent, data) {
        var nucleus = parent.select(".background-layer").selectAll(".nucleus").data(data.nucleus);
        nucleus.enter().append("image").attr({
          "class": "nucleus",
          "x": model2px(W.NUCLEUS * -0.5),
          "y": model2px(H.NUCLEUS * -0.5),
          "width": model2px(W.NUCLEUS),
          "height": model2px(H.NUCLEUS),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/BG_Nucleus.svg"),
          "transform": translateFuncInv
        }).style("opacity", opacityFunc);
        d3.transition(nucleus)
          .attr("transform", translateFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(nucleus.exit()).remove();
      },

      ribosomeBottom: function (parent, data) {
        var selection = parent.select(".over-dna-layer").selectAll(".ribosome-bottom").data(data.ribosomeBottom);
        selection.enter().append("image").attr({
          "class": "ribosome-bottom",
          "x": model2px(W.RIBO_BOTTOM * -0.5),
          "y": model2px(H.RIBO_BOTTOM * -0.5),
          "width": model2px(W.RIBO_BOTTOM),
          "height": model2px(H.RIBO_BOTTOM),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/Ribosome_bottom1.svg"),
          "transform": translateFuncInv
        }).style("opacity", 0);
        d3.transition(selection)
          .attr("transform", translateFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(selection.exit())
          .style("opacity", 0)
          .remove();
      },

      ribosomeTop: function (parent, data) {
        var selection = parent.select(".over-dna-layer").selectAll(".ribosome-top").data(data.ribosomeTop);
        selection.enter().append("image").attr({
          "class": "ribosome-top",
          "x": model2px(W.RIBO_TOP * -0.5),
          "y": model2px(H.RIBO_TOP * -0.5),
          "width": model2px(W.RIBO_TOP),
          "height": model2px(H.RIBO_TOP),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/Ribosome_top1.svg"),
          "transform": translateFuncInv
        }).style("opacity", 0);
        d3.transition(selection)
          .attr("transform", translateFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(selection.exit())
          .style("opacity", 0)
          .remove();
      },

      ribosomeUnder: function (parent, data) {
        var selection = parent.select(".under-dna-layer").selectAll(".ribosome-under").data(data.ribosomeUnder);
        selection.enter().append("image").attr({
          "class": "ribosome-under",
          "x": model2px(W.RIBO_UNDER * -0.5),
          "y": model2px(H.RIBO_UNDER * -0.5),
          "width": model2px(W.RIBO_UNDER),
          "height": model2px(H.RIBO_UNDER),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/Ribosome_under.png"),
          "transform": translateFuncInv
        }).style({
          "opacity": opacityFunc
        });
        d3.transition(selection)
          .attr("transform", translateFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(selection.exit()).remove();
      },

      ribosomeOver: function (parent, data) {
        var selection = parent.select(".over-dna-layer").selectAll(".ribosome-over").data(data.ribosomeOver);
        selection.enter().append("image").attr({
          "class": "ribosome-over",
          "x": model2px(W.RIBO_OVER * -0.5),
          "y": model2px(H.RIBO_OVER * -0.5),
          "width": model2px(W.RIBO_OVER),
          "height": model2px(H.RIBO_OVER),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/Ribosome_over.png"),
          "transform": translateFuncInv
        }).style("opacity", opacityFunc);
        d3.transition(selection)
          .attr("transform", translateFuncInv)
          .style("opacity", opacityFunc);
        d3.transition(selection.exit()).remove();
      },

      trna: function (parent, data) {
        var geneticEngine = model.geneticEngine(),

            codonWidth = 3 * nucleotides.WIDTH,
            offset = (codonWidth - W.TRNA) * 0.55,

            selection, enter, update, exit;

        selection = parent.select(".top-layer").selectAll(".trna")
                      .data(data.trna, function (d) { return d.index; });
        // The most outer container can be used to set easily position offset.
        // While the inner g elements provides translation for "ideal" tRNA position
        // close to the mRNA and optional rotation.
        enter = selection.enter().append("g").attr({
          "class": "trna",
          "display": function (d) { return d.index < 0 ? "none" : "inline"; },
          "transform": function (d, i) {
            return "translate(" + model2px(nucleotides.HEIGHT * 2) + ", " + model2px(-2.78) + ") " +
                    translateFuncInv(d, i) + " rotate(30)";
          }
        }).style("opacity", opacityFunc);

        enter.append("g")
          .attr("transform", "translate(0, " + model2px(-H.A) + ")")
          .call(nucleotides()
                  .model2px(model2px)
                  .sequence(function (d) { return geneticEngine.codonComplement(d.index); })
                  .backbone(false)
                  .randomEnter(false));

        enter.append("image").attr({
          "class": "trna-neck",
          "x": model2px(0.52 * (codonWidth - W.TRNA_NECK)),
          "y": model2px(-H.TRNA_NECK -H.TRNA * 0.95 - H.A * 0.92),
          "width": model2px(W.TRNA_NECK),
          "height": model2px(H.TRNA_NECK),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/tRNA_neck.png")
        });
        enter.append("image").attr({
          "class": "trna-base",
          "x": model2px(offset),
          "y": model2px(-H.TRNA - H.A * 0.92),
          "width": model2px(W.TRNA),
          "height": model2px(H.TRNA),
          "preserveAspectRatio": "none",
          "xlink:href": resourcesUrl("dna/tRNA_base.png")
        });

        update = d3.transition(selection)
          .attr("transform", translateFuncInv)
          .style("opacity", opacityFunc);
        update.select(".trna-neck").style("opacity", function (d) { return d.neck; });

        exit = d3.transition(selection.exit())
          .attr("transform", function (d, i) {
            return "translate(" + model2px(nucleotides.HEIGHT * -5) + ", " + model2px(nucleotides.HEIGHT * -4) + ") " +
                    translateFuncInv(d, i) + " rotate(-30)";
          })
          .style("opacity", 0);
        exit.selectAll(".bonds").style("opacity", 0);
        exit.remove();
      },

      viewPort: function (parent, data) {
        var position = data.viewPort[0].position,
            ease     = data.viewPort[0].ease,
            drag     = data.viewPort[0].drag,
            viewport = d3.transition(d3.select(node));

        // This is a bit hacky. In fact we use d3 transitions to modify model,
        // not the SVG element! It could be implemented also as a MD2D modeler
        // transition (see atom transitions), but this approach fits well our
        // needs and we reuse a lot of nice d3.transition features. We could
        // also consider removing atom transitions completely and use similar
        // approach for them. As this is just one exception, leave it for now,
        // but in the future it could be useful to chose only one approach
        // (d3.transitions vs custom transitions handled by MD2D modeler).

        // Update dragging behavior. Limit dragging to X axis.
        model.set("viewPortDrag", drag ? "x" : false);
        // Duck test whether viewportUpdate is a transition or selection.
        // See D3 API Reference - d3.transition(selection) returns transition
        // only when called in the context of other transition. Otherwise it
        // returns selection.
        if (viewport.duration) {
          // Transition!
          viewport.ease(ease);
          viewport.tween("model-update", function () {
            var i = d3.interpolate(Number(model.properties.viewPortX), position * nucleotides.WIDTH);
            return function (t) {
              var newVal = i(t);
              if (Math.abs(model.properties.viewPortX - newVal) > 1e-3) {
                model.properties.viewPortX = newVal;
              }
            };
          });
        } else {
          // Selection! Immediate update of the model.
          model.set("viewPortX", position * nucleotides.WIDTH);
        }
      },

      background: function (parent, data) {
        appendTranscriptionBg(parent);
        d3.transition(d3.select(node).select(".container-background")).attr("fill", data.background[0].color);
      }
    };
  }

  GeneticElementsRenderer.W = W;
  GeneticElementsRenderer.H = H;
  GeneticElementsRenderer.OBJECT_NAMES = OBJECT_NAMES;

  return GeneticElementsRenderer;
});

/*global define, $ */

define('common/views/state-manager',[],function () {
  return function StateManager(names) {
    var api,
        states = [],
        stateByName = {};

    api = {
      newState: function (stateName, stateDef) {
        var state = $.extend(true, {}, stateDef);
        names.forEach(function (n) {
          if (typeof state[n] === "undefined") {
            state[n] = [];
          }
        });
        states.push(state);
        stateByName[stateName] = state;
      },
      extendLastState: function (stateName, stateDef) {
        var prevState = states[states.length - 1],
            state = {};

        names.forEach(function (n) {
          state[n] = [];
          if (typeof stateDef[n] !== "undefined") {
            // Array expected!
            stateDef[n].forEach(function (objDef, idx) {
              state[n].push($.extend(true, {}, prevState[n][idx], objDef));
            });
          }
        });
        states.push(state);
        stateByName[stateName] = state;
      },
      getState: function (name) {
        var orgState = stateByName[name],
            state = $.extend(true, {}, orgState),
            objName;
        for (objName in state) {
          if (state.hasOwnProperty(objName)) {
            state[objName].forEach(function (d, i) {
              var value;
              for (value in d) {
                if (typeof d[value] === "function") {
                  // Very important - evaluate function from original state
                  // object, not from copy! It can be important where two
                  // functions call each other. It should still work.
                  d[value] = orgState[objName][i][value]();
                }
              }
            });
          }
        }
        return state;
      }
    };
    return api;
  };
});

/*global define */

define('models/md2d/views/genetic-anim-states',['require','models/md2d/views/nucleotides','models/md2d/views/genetic-elements-renderer','common/views/state-manager'],function (require) {
  var nucleotides             = require('models/md2d/views/nucleotides'),
      GeneticElementsRenderer = require('models/md2d/views/genetic-elements-renderer'),
      StateManager            = require('common/views/state-manager'),

      H = GeneticElementsRenderer.H,
      OBJECT_NAMES = GeneticElementsRenderer.OBJECT_NAMES;

  /**
   * Returns StateManager with definitions of DNA / genetic animations states.
   */
  return function geneticAnimStates(model) {
    var stateMgr       = new StateManager(OBJECT_NAMES),
        geneticEngine  = model.geneticEngine(),
        // Viewport dimensions are immutable, so save them once.
        viewPortWidth  = model.get("viewPortWidth"),
        viewPortHeight = model.get("viewPortHeight"),
        vx = viewPortWidth * 0.5,
        vy = viewPortHeight * 0.5,

        lastStep;

    function getStep() {
      var state = geneticEngine.state();
      if (state.name === "translation-end") {
        return model.geneticEngine().lastTranslationStep();
      }
      lastStep = !isNaN(state.step) ? state.step : lastStep;
      return lastStep;
    }
    function ribosomeX() {
      return (1.65 + Math.max(0, getStep() - 2) * 3) * nucleotides.WIDTH;
    }
    function trnaX() {
      return this.index() * 3 * nucleotides.WIDTH;
    }

    stateMgr.newState("intro-cells", {
      cells: [{
        translateX: vx + 0.33,
        translateY: vy,
        scale: 1
      }],
      dna1: [{
        translateX: vx + 0.33,
        translateY: vy,
        scale: 0.13,
        opacity: 0
      }],
      viewPort: [{
        position: 0,
        ease: "cubic-in-out",
        drag: false
      }],
      background: [{
        color: "#8492ef"
      }]
    });
    stateMgr.extendLastState("intro-zoom1", {
      cells: [{
        translateX: vx,
        scale: 6
      }],
      dna1: [{
        translateX: vx,
        scale: 0.78,
        opacity: 5
      }],
      dna2: [{
        translateX: vx,
        translateY: vy,
        scale: 0.5,
        opacity: 0
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("intro-zoom2", {
      cells: [{
        scale: 24
      }],
      dna1: [{
        scale: 3.12,
        opacity: 0
      }],
      dna2: [{
        scale: 2,
        opacity: 1
      }],
      dna3: [{
        translateX: vx,
        translateY: vy,
        scale: 0.2,
        opacity: 0
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("intro-zoom3-s0", {
      cells: [{}],
      dna2: [{
        scale: 3.8,
        opacity: 0
      }],
      dna3: [{
        scale: 0.4,
        opacity: 1
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("intro-zoom3", {
      cells: [{}],
      dna3: [{
        scale: 0.6
      }],
      polymeraseUnder: [{
        scale: 0.2,
        translateX: -2,
        translateY: 4,
        opacity: 1
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("intro-polymerase-s0", {
      cells: [{}],
      dna3: [{}],
      polymeraseUnder: [{
        scale: 0.8,
        translateX: vx,
        translateY: vy,
        opacity: 1
      }],
      polymeraseOver: [{
        translateX: vx,
        translateY: vy,
        scale: 0.8,
        opacity: 0
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("intro-polymerase", {
      cells: [{}],
      dna3: [{}],
      polymeraseUnder: [{
        scale: 1,
      }],
      polymeraseOver: [{
        scale: 1,
        opacity: 1
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("dna-s0", {
      cells: [{
        opacity: 0
      }],
      dna3: [{
        scale: 1.5
      }],
      polymeraseUnder: [{
        scale: 2.5
      }],
      polymeraseOver: [{
        scale: 2.5
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("dna", {
      polymeraseUnder: [{
        opacity: 0
      }],
      polymeraseOver: [{
        opacity: 0
      }],
      dna3: [{
        opacity: 0
      }],
      dna: [{
        translateX: -model.geneticEngine().PRECODING_LEN * nucleotides.WIDTH,
        translateY: viewPortHeight / 2 + nucleotides.HEIGHT,
        bonds: 1
      }],
      dnaComp: [{
        translateX: -model.geneticEngine().PRECODING_LEN * nucleotides.WIDTH,
        translateY: viewPortHeight / 2 - nucleotides.HEIGHT,
        bonds: 1
      }],
      viewPort: [{
        position: -2,
        drag: true
      }],
      background: [{
        color: "url(#transcription-bg)"
      }]
    });
    stateMgr.extendLastState("transcription", {
      dna: [{
        translateY: viewPortHeight / 2 + 2.5 * nucleotides.HEIGHT,
        bonds: 0
      }],
      dnaComp: [{
        translateY: viewPortHeight / 2 - 2.5 * nucleotides.HEIGHT,
        bonds: function () {
          var limit = getStep() + model.geneticEngine().PRECODING_LEN;
          return function (d) {
            return d.region === "c" && d.idx < limit ? 1 : 0;
          };
        }
      }],
      mrna: [{
        translateY: viewPortHeight / 2 - 0.5 * nucleotides.HEIGHT,
        bonds: 1,
        direction: 1
      }],
      viewPort: [{
        position: function () {
          return Math.max(0, Math.min(model.get("DNA").length - 10, getStep() - 6)) - 2;
        },
        ease: "linear"
      }],
      background: [{}]
    });
    stateMgr.extendLastState("transcription-end", {
      dna: [{}],
      dnaComp: [{
        bonds: function () {
          return function (d) {
            return d.region === "c" ? 1 : 0;
          };
        }
      }],
      mrna: [{}],
      polymeraseUnder: [{
        translateX: function () { return model.get("DNA").length * nucleotides.WIDTH; },
        translateY: 0.5 * viewPortHeight,
        scale: 3.5,
        opacity: 0
      }],
      polymeraseOver: [{
        translateX: function () { return model.get("DNA").length * nucleotides.WIDTH; },
        translateY: 0.5 * viewPortHeight,
        scale: 3.5,
        opacity: 0
      }],
      viewPort: [{
        position: function () { return Math.max(0, model.get("DNA").length - 10) - 2; }
      }],
      background: [{}]
    });
    stateMgr.extendLastState("after-transcription", {
      dna: [{}],
      dnaComp: [{}],
      mrna: [{}],
      polymeraseUnder: [{
        opacity: 1
      }],
      polymeraseOver: [{
        opacity: 1
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("before-translation-s0", {
      dna: [{}],
      dnaComp: [{}],
      mrna: [{}],
      polymeraseUnder: [{
        scale: 1.4
      }],
      polymeraseOver: [{
        scale: 1.4,
        opacity: 0
      }],
      viewPort: [{}],
      background: [{
        color: "#8492ef"
      }]
    });
    stateMgr.extendLastState("before-translation-s1", {
      dna: [{}],
      dnaComp: [{}],
      mrna: [{}],
      polymeraseUnder: [{
        translateX: function () { return model.get("viewPortX") + 0.5 * viewPortWidth + 5; }, // + 5!
        translateY: 0.5 * viewPortHeight - 2,
        scale: 0.7
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("before-translation-s2", {
      dna: [{}],
      dnaComp: [{}],
      mrna: [{}],
      nucleus: [{
        translateX: 0.5 * viewPortWidth - 2 * nucleotides.WIDTH,
        translateY: 0.5 * viewPortHeight
      }],
      viewPort: [{
        position: -2,
        ease: "cubic-in-out"
      }],
      background: [{}]
    });
    stateMgr.extendLastState("before-translation-s3", {
      dna: [{
        translateY: 4 * nucleotides.HEIGHT
      }],
      dnaComp: [{
        translateY: 2 * nucleotides.HEIGHT,
        bonds: 0
      }],
      mrna: [{
        bonds: 0
      }],
      nucleus: [{
        translateY: 0
      }],
      viewPort: [{}],
      background: [{
        color: function() { return model.get("backgroundColor"); }
      }]
    });
    stateMgr.extendLastState("before-translation-s4", {
      dna: [{
        translateY: -1 * nucleotides.HEIGHT
      }],
      dnaComp: [{
        translateY: -3 * nucleotides.HEIGHT,
      }],
      mrna: [{
        translateY: 2.5 * nucleotides.HEIGHT
      }],
      nucleus: [{
        translateY: H.NUCLEUS * -0.5
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("before-translation", {
      mrna: [{
        translateY: 1.5 * nucleotides.HEIGHT,
        direction: 2,
        bonds: 0
      }],
      ribosomeBottom: [{
        translateX: -3,
        translateY: vy,
        opacity: 0
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-s0", {
      mrna: [{}],
      ribosomeBottom: [{
        translateX: ribosomeX,
        translateY: 1.75 * nucleotides.HEIGHT,
        opacity: 1
      }],
      ribosomeTop: [{
        translateX: -3,
        translateY: 6,
        opacity: 0
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-s1", {
      mrna: [{}],
      ribosomeBottom: [{}],
      ribosomeTop: [{
        translateX: ribosomeX,
        translateY: 4.52 * nucleotides.HEIGHT,
        opacity: 1
      }],
      ribosomeUnder: [{
        translateX: ribosomeX,
        translateY: 3.7 * nucleotides.HEIGHT,
        opacity: 0
      }],
      ribosomeOver: [{
        translateX: ribosomeX,
        translateY: 3.7 * nucleotides.HEIGHT,
        opacity: 0
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation", {
      mrna: [{
        bonds: function () {
          var step = getStep();
          return function (d) {
            return d.idx < 3 * (step - 2) || d.idx >= 3 * step ? 0 : 1;
          };
        }
      }],
      ribosomeUnder: [{
        opacity: 1
      }],
      ribosomeOver: [{
        opacity: 1
      }],
      trna: [
        {
          index: function () { return getStep() - 2; },
          translateX: trnaX,
          translateY: 2.5 * nucleotides.HEIGHT,
          neck: 0
        },
        {
          index: function () { return getStep() - 1; },
          translateX: trnaX,
          translateY: 2.5 * nucleotides.HEIGHT,
          neck: 1
        }
      ],
      viewPort: [{
        position: function () { return Math.max(0, 3 * (getStep() - 3)) - 2; },
        ease: "linear"
      }],
      background: [{}]
    });
    stateMgr.extendLastState("translation-step0", {
      mrna: [{
        bonds: function () {
          var step = getStep();
          return function (d) {
            return d.idx < 3 * (step - 3) || d.idx >= 3 * step ? 0 : 1;
          };
        }
      }],
      ribosomeUnder: [{}],
      ribosomeOver: [{}],
      trna: [
        {
          index: function () { return getStep() - 3; },
        },
        {
          index: function () { return getStep() - 2; },
        },
        {
          index: function () { return getStep() - 1; },
          translateX: trnaX,
          translateY: 2.5 * nucleotides.HEIGHT,
          neck: 1
        }
      ],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-step1", {
      mrna: [{}],
      ribosomeUnder: [{}],
      ribosomeOver: [{}],
      trna: [
        {},
        {
          neck: 0
        },
        {}
      ],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-end-s0", {
      mrna: [{}],
      ribosomeUnder: [{}],
      ribosomeOver: [{}],
      trna: [
        {
          index: function () { return getStep() - 2; }
        },
        {
          index: function () { return getStep() - 1; },
          neck: 0
        }
      ],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-end-s1", {
      mrna: [{
        bonds: function () {
          var step = getStep();
          return function (d) {
            return d.idx < 3 * (step - 1) || d.idx >= 3 * step ? 0 : 1;
          };
        }
      }],
      ribosomeUnder: [{}],
      ribosomeOver: [{}],
      trna: [{
        index: function () { return getStep() - 1; },
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-end-s2", {
      mrna: [{
        bonds: 0
      }],
      ribosomeUnder: [{}],
      ribosomeOver: [{}],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-end-s3", {
      mrna: [{}],
      ribosomeBottom: [{
        translateX: ribosomeX,
        translateY: 1.75 * nucleotides.HEIGHT,
        opacity: 1
      }],
      ribosomeTop: [{
        translateX: ribosomeX,
        translateY: 4.52 * nucleotides.HEIGHT,
        opacity: 1
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-end-s4", {
      mrna: [{}],
      ribosomeBottom: [{
        translateY: 1.75 * nucleotides.HEIGHT - 0.3,
      }],
      ribosomeTop: [{
        translateY: 4.52 * nucleotides.HEIGHT + 0.5,
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-end-s5", {
      mrna: [{}],
      ribosomeBottom: [{
        translateX: function () { return ribosomeX() + 8; },
        translateY: 1.75 * nucleotides.HEIGHT - 0.5,
      }],
      ribosomeTop: [{
        translateX: function () { return ribosomeX() + 8; },
        translateY: 4.52 * nucleotides.HEIGHT + 5,
      }],
      viewPort: [{}],
      background: [{}]
    });
    stateMgr.extendLastState("translation-end", {
      mrna: [{}],
      viewPort: [{}],
      background: [{}]
    });

    return stateMgr;
  };
});

/*global define, $ */

/**
 * Require this module to add support of SVG elements in following jQuery functions:
 * - .hasClass(className)
 * - .addClass(className)
 * - .removeClass(className)
 * Note that only the basic form (argument is a string with class name) of these functions is
 * supported for SVG! E.g. if you pass function as an argument (what is supported by jQuery),
 * original jQuery function without SVG support will be executed.
 *
 * TEST: http://jsfiddle.net/aKJvc/
 * If you update code below, you can use this jsfiddle and test especially in Safari 7,
 * which was causing problems.
 */
define('common/jquery-svg-class',[],function () {

  // Save original jQuery functions (they will be used for HTML elements).
  var hasClassHTML    = $.fn.hasClass;
  var addClassHTML    = $.fn.addClass;
  var removeClassHTML = $.fn.removeClass;
  // Implement functions that work fine with SVGElements.
  var hasClassSVG = function (className) {
    return new RegExp('(\\s|^)' + className + '(\\s|$)').test(this.getAttribute('class'));
  };
  var addClassSVG = function (className) {
    if (!hasClassSVG.apply(this, arguments)) {
      this.setAttribute('class', this.getAttribute('class') + ' ' + className);
    }
  };
  var removeClassSVG = function (className) {
    var removedClass = this.getAttribute('class').replace(new RegExp('(\\s|^)' + className + '(\\s|$)', 'g'), '$2');
    if (hasClassSVG.apply(this, arguments)) {
      this.setAttribute('class', removedClass);
    }
  };

  // Executes function suitable for SVG or HTML element.
  function execute(jquerySelection, orgArguments, SVGFunc, HTMLFunc) {
    var result = [];
    var element;
    for(var i = 0, len = jquerySelection.length; i < len; i++) {
      element = jquerySelection[i];
      if (element instanceof SVGElement) {
        result.push(SVGFunc.apply(element, orgArguments));
      } else {
        result.push(HTMLFunc.apply($(element), orgArguments));
      }
    }
    return result;
  }

  // Overwrite jQuery functions:
  $.fn.hasClass = function(value) {
    var result = execute(this, arguments, hasClassSVG, hasClassHTML);
    for (var i = 0, len = result.length; i < len; i++) {
      if (result[i]) return true;
    }
    return false;
  };

  $.fn.addClass = function(value) {
    if ($.isFunction(value)) {
      // No support for function argument and SVGElement.
      return addClassHTML.apply(this, arguments);
    }
    execute(this, arguments, addClassSVG, addClassHTML);
    return this;
  };

  $.fn.removeClass = function(value) {
    if ($.isFunction(value)) {
      // No support for function argument and SVGElement.
      return removeClassHTML.apply(this, arguments);
    }
    execute(this, arguments, removeClassSVG, removeClassHTML);
    return this;
  };
});



/*
Simple module which provides mutations context menu for DNA nucleotides.

CSS style definition: sass/lab/_context-menu.sass
*/


(function() {
  var __hasProp = {}.hasOwnProperty;

  define('cs!models/md2d/views/mutations-context-menu',['require','common/jquery-svg-class'],function(require) {
    require('common/jquery-svg-class');
    return {
      /*
        Registers context menu for DOM elements defined by @selector.
        @model should be an instance of Modeler class (MD2D Modeler).
        @DNAComplement indicates whether this menu is registered for
        DNA or DNA complementary strand.
      */

      register: function(selector, model, DNAComplement, i18n) {
        var clickedNucleoType, onMenuHide, onMenuShow;
        clickedNucleoType = null;
        onMenuShow = function(options) {
          var item, key, subsItems;
          clickedNucleoType = d3.select(options.$trigger[0]).datum().type;
          subsItems = options.items["Substitution"].items;
          for (key in subsItems) {
            if (!__hasProp.call(subsItems, key)) continue;
            item = subsItems[key];
            key = key.split(":")[1];
            item.$node.addClass("" + clickedNucleoType + "-to-" + key);
          }
          d3.select(options.$trigger[0]).classed("glowing", true);
          return true;
        };
        onMenuHide = function(options) {
          var item, key, subsItems;
          subsItems = options.items["Substitution"].items;
          for (key in subsItems) {
            if (!__hasProp.call(subsItems, key)) continue;
            item = subsItems[key];
            key = key.split(":")[1];
            item.$node.removeClass("" + clickedNucleoType + "-to-" + key);
          }
          d3.select(options.$trigger[0]).classed("glowing", false);
          return true;
        };
        $.contextMenu("destroy", selector);
        $.contextMenu({
          selector: selector,
          appendTo: ".lab-responsive-content",
          className: "mutations-menu",
          trigger: "left",
          events: {
            show: onMenuShow,
            hide: onMenuHide
          },
          callback: function(key, options) {
            var d;
            key = key.split(":");
            d = d3.select(options.$trigger[0]).datum();
            switch (key[0]) {
              case "substitute":
                return model.geneticEngine().mutate(d.idx, key[1], DNAComplement);
              case "insert":
                return model.geneticEngine().insert(d.idx, key[1], DNAComplement);
              case "delete":
                return model.geneticEngine()["delete"](d.idx);
            }
          },
          items: {
            "Substitution": {
              name: i18n.t("md2d.mutations_menu.substitution_mutation"),
              className: "substitution-submenu",
              items: {
                "substitute:A": {
                  name: ""
                },
                "substitute:T": {
                  name: ""
                },
                "substitute:G": {
                  name: ""
                },
                "substitute:C": {
                  name: ""
                }
              }
            },
            "Insertion": {
              name: i18n.t("md2d.mutations_menu.insertion_mutation"),
              className: "insertion-submenu",
              items: {
                "insert:A": {
                  name: i18n.t("md2d.mutations_menu.insert"),
                  className: "A"
                },
                "insert:T": {
                  name: i18n.t("md2d.mutations_menu.insert"),
                  className: "T"
                },
                "insert:G": {
                  name: i18n.t("md2d.mutations_menu.insert"),
                  className: "G"
                },
                "insert:C": {
                  name: i18n.t("md2d.mutations_menu.insert"),
                  className: "C"
                }
              }
            },
            "delete": {
              name: i18n.t("md2d.mutations_menu.deletion_mutation")
            }
          }
        });
      }
    };
  });

}).call(this);

/*global define, d3 */

define('models/md2d/views/genetic-renderer',['require','models/md2d/views/nucleotides','models/md2d/views/genetic-elements-renderer','models/md2d/views/genetic-anim-states','cs!models/md2d/views/mutations-context-menu'],function (require) {
  var nucleotides             = require('models/md2d/views/nucleotides'),
      GeneticElementsRenderer = require('models/md2d/views/genetic-elements-renderer'),
      GeneticAnimStates       = require('models/md2d/views/genetic-anim-states'),
      mutationsContextMenu    = require('cs!models/md2d/views/mutations-context-menu'),

      OBJECT_NAMES = GeneticElementsRenderer.OBJECT_NAMES;

  // Implement .interrupt() method that cancels all currently scheduled
  // transitions. Based on Mike's idea:
  // https://github.com/mbostock/d3/issues/1410#issuecomment-21251284
  d3.selection.prototype.interrupt = function() {
    return this.each(function() {
      var lock = this.__transition__;
      if (lock) {
        var active = -1;
        for (var id in lock) if ((id = +id) > active) active = id;
        lock.active = active + 1;
      }
    });
  };

  function GeneticRenderer(modelView, model) {
    var api,
        node = modelView.node,
        model2px = modelView.model2px,
        model2pxInv = modelView.model2pxInv,
        viewportG = d3.select(node).select(".below-atoms"),

        g = null,
        currentTrans = null,
        state = null,
        prevAnimState = null,
        prevAnimStep = null,
        suppressViewport = false,
        transitionInProgress = false,
        animStateInProgress = null,

        stateMgr = new GeneticAnimStates(model),
        objectRenderer = new GeneticElementsRenderer(node, model2px, model2pxInv, model),

        transitionFunction;

    function init() {
      // Redraw DNA / mRNA when DNA state is changed.
      model.geneticEngine().on("change", render);
      // Play animation when there is a "transition" event.
      model.geneticEngine().on("transition", transition);

      // When DNAMutations is changed, cleanup & render again.
      model.addPropertiesListener(["DNAMutations"], setup);

      // When viewPortX is changed render DNA and mRNA again. Also center
      // protein while in 'translation-end' state.
      model.addPropertiesListener(["viewPortX"], function() {
        // state.name === "transcription" && transitionInProgress is an icky
        // workaround for the problem with transitions when new nucleotide is
        // entering. Rendering performed by this function breaks the
        // animation. However, when state === "transcription", we actually doesn't
        // have to render DNA and mRNA, as it will be rendered anyway.
        if (!g || !model.get("DNA") || (state.name === "transcription" && transitionInProgress)) return;

        // state.name values are subset of all animation states. We define
        // more animation states than we publish for author / users
        // (animations with -s0, -s1, (...) suffixes).
        var data = stateMgr.getState(animStateInProgress || state.name);

        objectRenderer.mrna(g, data, true);
        objectRenderer.dna(g, data, true);
        objectRenderer.dnaComp(g, data, true);

        if (!transitionInProgress &&
            state.name === "translation-end" &&
            model.getNumberOfAtoms() > 0) {
          model.geneticEngine().centerProtein();
        }
      });

      // Register mutation menus for DNA and DNA complement. Note that
      // jQuery.contextMenu uses event delegation, so it's fully enough to
      // register this menu only once, even before these elements exists.
      mutationsContextMenu.register('[class~="dna"] [class~="clickable-nucleo"]', model, false, modelView.i18n);
      mutationsContextMenu.register('[class~="dna-comp"] [class~="clickable-nucleo"]', model, true, modelView.i18n);
    }

    /**
     * Setups genetic renderer. It will be called when new parent view is created
     * or reseted.
     *
     * @private
     */
    function setup() {
      state = model.geneticEngine().state();

      // Cleanup.
      cancelTransitions();
      viewportG.selectAll("g.genetics").remove();
      g = null;

      if (!model.get("DNA")) {
        // When DNA is not defined (=== "", undefined or null) genetic
        // renderer has nothing to do.
        return;
      }

      // Create a new container.
      g = viewportG.insert("g", ".image-container-below").attr("class", "genetics");
      g.append("g").attr("class", "background-layer");
      g.append("g").attr("class", "under-dna-layer");
      g.append("g").attr("class", "dna-layer");
      g.append("g").attr("class", "over-dna-layer");
      g.append("g").attr("class", "top-layer");

      render();
    }

    /**
     * Renders DNA-related graphics using "DNA" and "DNAState"
     * options of the model.
     */
    function render(suppressViewportUpdate) {
      suppressViewport = suppressViewportUpdate;

      // Update DNA state.
      state = model.geneticEngine().state();

      cancelTransitions();

      // Force rendering of all objects when render was called before previous
      // transition ended. This means that we can be somewhere between states
      // and it's impossible to detect which objects should be rendered using
      // previous and current animation state.
      renderState(g, state.name, null, transitionInProgress);

      transitionInProgress = false;
    }

    /**
     * Renders animation state. It updates all objects from previous and new state.
     *
     * You can pass d3.selection or d3.transition as "parent" argument to decide whether
     * new state should be rendered immediately or using transition.
     *
     * @private
     * @param {d3.selection OR d3.transition} parent d3.selection or d3.transition object.
     * @param {String} animState  animation state name.
     * @param {function} onStartCallback callback executed at the beginning of transition
     *                                   or immediately if parent isn't a transition (optional).
     * @param {boolean} forceAll forces re-rendering of all scene objects (optional).
     */
    function renderState(parent, animState, onStartCallback, forceAll) {
      var data = stateMgr.getState(animState),
          prevAnimStateData = prevAnimState ? stateMgr.getState(prevAnimState) : null;

      // TODO: make it simpler.
      function shouldRenderObj(name) {
        var inData     = !!data[name].length,
            inPrevData = !!(prevAnimStateData && prevAnimStateData[name].length);

        if (suppressViewport && name === "viewPort") {
          // Viewport update can be disabled using special variable.
          return false;
        } else if (forceAll || inData || inPrevData) {
          // Render all objects from current and previous states.
          return true;
        }
        return false;
      }

      function render() {
        parent.each(function() {
          var parent = d3.select(this);
          OBJECT_NAMES.forEach(function (name) {
            if (shouldRenderObj(name)) {
              objectRenderer[name](parent, data);
            }
          });
        });
        if (onStartCallback) onStartCallback(parent);
      }

      if (parent.duration) {
        // Transition.
        parent.each("start.transition-name", function () {
          animStateInProgress = animState;
          render();
        });
        parent.each("end.transition-name", function () {
          animStateInProgress = null;
        });
      } else {
        render();
      }

      prevAnimState = animState;
      prevAnimStep = state.step || 0; // when undefined or NaN
    }

    /**
     * Returns a new, chained transition.
     * This transition will be executed when previous one ends.
     *
     * @private
     * @return {d3 transtion} d3 transtion object.
     */
    function nextTrans(forceNew) {
      var newTrans;
      // TODO: this first check is a workaround.
      // Ideal scenario would be to call always:
      // currentTrans[name] = currentTrans[name].transition();
      // but it seems to fail when transition has already ended.
      if (!forceNew && currentTrans && currentTrans.node().__transition__) {
        // Some transition is currently in progress, chain a new transition.
        newTrans = currentTrans.transition();
      } else {
        // All transitions ended, just create a new one.
        newTrans = g.transition();
      }
      currentTrans = newTrans;
      return newTrans;
    }

    function cancelTransitions() {
      var d3node = d3.select(node);
      var g = d3node.select("g.genetics");
      if (!g.empty() && g.node().__transition__) {
       // Note that some transitions can be applied to elements that live outside g.genetics
       // element, e.g. background. So, it isn't enough to use d3.selectAll("g.genetics *").
        d3node.selectAll("g.genetics, g.genetics *").interrupt();
        d3node.select(".container-background").interrupt(); // background changes
        currentTrans = null;
        animStateInProgress = null;
      }
    }

    /**
     * Triggers animation state transition.
     */
    function transition(transitionName, suppressViewportUpdate) {
      transitionInProgress = true;
      suppressViewport = suppressViewportUpdate;

      // Update DNA state.
      state = model.geneticEngine().state();

      if (Number(transitionName.split(":")[1]) > 0) {
        // e.g. translation:5 or transcription:7
        // We have one common transition function for all "transcription:1" to
        // "transcription:N" transitions called "transcription", as well as
        // one common transition function for all "translation:1" to
        // "translation:N" transitions called "translation".
        transitionName = transitionName.split(":")[0];
      }

      transitionFunction[transitionName]();

      currentTrans.each("end.trans-end", function() {
        transitionInProgress = false;
        // Notify engine that transition has ended.
        model.geneticEngine().transitionEnded();
      });
    }

    /**
     * Definition of all transition functions.
     * @private
     * @type {Object}
     */
    transitionFunction = {
      "dna-updated": function dnaUpdated() {
        // Special state - render current animation state again,
        // as model was updated.
        var t = nextTrans(true).ease("cubic-in-out").duration(800);
        renderState(t, state.name, null, true);
      },

      "intro-zoom1": function introZoom1() {
        var t = nextTrans(true).ease("cubic").duration(3000);
        renderState(t, "intro-zoom1");
      },

      "intro-zoom2": function introZoom2() {
        var t = nextTrans(true).ease("linear").duration(3000);
        renderState(t, "intro-zoom2");
      },

      "intro-zoom3": function introZoom3() {
        var t = nextTrans(true).ease("linear").duration(2000);
        renderState(t, "intro-zoom3-s0");

        t = nextTrans().ease("quad-out").duration(3300);
        renderState(t, "intro-zoom3");
      },

      "intro-polymerase": function introPolymerase() {
        var t = nextTrans(true).ease("quad-out").duration(3000);
        renderState(t, "intro-polymerase-s0");

        t = nextTrans().ease("cubic-in-out").duration(1000);
        renderState(t, "intro-polymerase");
      },

      "dna": function dna() {
        var t = nextTrans(true).duration(2000);
        renderState(t, "dna-s0");


        t = nextTrans().duration(1000);
        renderState(t, "dna", function (t) {
          // Make some transitions almost immediate.
          t.selectAll(".nucleotide").duration(5);
          t.selectAll(".container-background").duration(5);
        });
      },

      "transcription:0": function transcription0() {
        var t = nextTrans(true).duration(1500);
        renderState(t, "transcription", function(t) {
          // Reselect bonds transition, change duration to 250.
          t.selectAll(".bonds").duration(250);
        });
      },

      "transcription": function transcription() {
        var t = nextTrans(true).duration(500);
        renderState(t, "transcription", function (t) {
          // Reselect bonds transition, change duration to ease to cubic.
          t.selectAll(".bonds").ease("cubic");
        });
      },

      "transcription-end": function transcriptionEnd() {
        var t = nextTrans(true).duration(500);
        renderState(t, "transcription-end", function (t) {
          // Reselect bonds transition, change duration to ease to cubic.
          t.selectAll(".bonds").ease("cubic");
        });
      },

      "after-transcription": function afterTranscription() {
        var t = nextTrans(true).ease("cubic-in-out").duration(700);
        renderState(t, "after-transcription");
      },

      "before-translation": function beforeTranslation() {
        var t = nextTrans(true).ease("cubic-in-out").duration(1000);
        renderState(t, "before-translation-s0", function (t) {
          t.selectAll(".container-background").duration(1);
        });

        t = nextTrans().ease("cubic-in-out").duration(1500);
        renderState(t, "before-translation-s1");

        t = nextTrans().ease("cubic-in-out").duration(1500);
        renderState(t, "before-translation-s2");

        t = nextTrans().ease("cubic").duration(1000);
        renderState(t, "before-translation-s3", function (t) {
          t.selectAll(".bonds").duration(250);
          t.selectAll(".container-background").duration(1);
        });

        t = nextTrans().ease("cubic-out").duration(1000);
        renderState(t, "before-translation-s4");

        t = nextTrans().ease("cubic-out").duration(500);
        renderState(t, "before-translation");
      },

      "translation:0": function translation0() {
        var t = nextTrans(true).ease("cubic-in-out").duration(1000);
        renderState(t, "translation-s0");

        t = nextTrans().ease("cubic-in-out").duration(1000);
        renderState(t, "translation-s1");

        t = nextTrans().ease("cubic-in-out").duration(500);
        renderState(t, "translation");
      },

      "translation": function translation() {
        var geneticEngine = model.geneticEngine(),
            codonIdx = state.step - 1,
            newAADuration = 1000,
            shiftDuration = 500,
            t;

        t = nextTrans(true).duration(newAADuration);
        renderState(t, "translation-step0", function (t) {
          t.selectAll(".bonds").ease("cubic");
          geneticEngine.translationStepStarted(codonIdx, 1.45 + codonIdx * 3 * nucleotides.WIDTH, 3.95,
              0.53 + codonIdx * 3 * nucleotides.WIDTH, 1.57, newAADuration);
        });

        t = nextTrans().duration(shiftDuration);
        renderState(t, "translation-step1", function (t) {
          t.selectAll(".trna-neck").duration(150);
          geneticEngine.shiftAminoAcids(codonIdx, 2 * nucleotides.WIDTH, shiftDuration);
        });

        // This will remove 3rd tRNA.
        if (codonIdx > 0) {
          t = nextTrans().duration(900);
          renderState(t, "translation", function (t) {
            geneticEngine.connectAminoAcid(codonIdx);
            t.selectAll(".bonds").duration(150);
          });
        }
      },

      "translation-end": function translationEnd() {
        var geneticEngine = model.geneticEngine(),
            aaCount = model.getNumberOfAtoms(),
            t;

        if (aaCount >= 1) {
          t = nextTrans(true).duration(150);
          renderState(t, "translation-end-s0");

          t = nextTrans().duration(800);
          renderState(t, "translation-end-s1", function (t) {
            geneticEngine.translationCompleted();
            t.selectAll(".bonds").duration(150);
          });

          t = nextTrans().duration(800);
          renderState(t, "translation-end-s2", function (t) {
            t.selectAll(".bonds").duration(150);
          });

          t = nextTrans().duration(500);
          renderState(t, "translation-end-s3");
        } else {
          t = nextTrans(true).duration(500);
          renderState(t, "translation-end-s3");
        }

        t = nextTrans().duration(300);
        renderState(t, "translation-end-s4");

        t = nextTrans().duration(1000);
        renderState(t, "translation-end-s5");

        t = nextTrans().duration(700);
        renderState(t, "translation-end", function () {
          geneticEngine.centerProtein(700);
        });
      }
    };

    api = {
      setup: setup,
      render: render
    };

    init();
    return api;
  }

  return GeneticRenderer;
});



/*
  A simple function to wrap a string of text into an SVG text node of a given width
  by creating tspans and adding words to them until the computedTextLength of the
  tspan is greater than the desired width. Returns the number of lines.

  If no wrapping is desired, use maxWidth=-1
*/


(function() {

  define('cs!common/layout/wrap-svg-text',['require'],function(require) {
    var svgUrl, wrapSVGText;
    svgUrl = "http://www.w3.org/2000/svg";
    return wrapSVGText = window.wrapSVGText = function(text, svgTextNode, maxWidth, x, dy) {
      var computedTextLength, curLineLength, dashArray, dashes, i, lastWord, line, newlinemode, numLines, result, tempText, textNode, tspanNode, widestWidth, width, word, words, _i, _len;
      dashes = /-/gi;
      if (text.search("\n") > 0) {
        words = text.split("\n");
        newlinemode = true;
        dashArray = [];
      } else {
        words = text.split(/[\s-]/);
        newlinemode = false;
        dashArray = (function() {
          var _results;
          _results = [];
          while (result = dashes.exec(text)) {
            _results.push(result.index);
          }
          return _results;
        })();
      }
      curLineLength = 0;
      computedTextLength = 0;
      numLines = 1;
      widestWidth = 0;
      for (i = _i = 0, _len = words.length; _i < _len; i = ++_i) {
        word = words[i];
        curLineLength += word.length + 1;
        if (i === 0 || newlinemode || maxWidth > 0 && computedTextLength > maxWidth) {
          if (i > 0) {
            if (newlinemode) {
              widestWidth = Math.max(tspanNode.getComputedTextLength(), widestWidth);
              numLines++;
            } else {
              tempText = tspanNode.firstChild.nodeValue;
              if (tempText.length > words[i - 1].length + 1) {
                lastWord = tempText.slice(tempText.length - words[i - 1].length - 1);
                tspanNode.firstChild.nodeValue = tempText.slice(0, tempText.length - words[i - 1].length - 1);
              } else if (tempText.length === words[i - 1].length + 1) {
                tspanNode.firstChild.nodeValue = tempText.slice(0, tempText.length - 1);
              }
              widestWidth = Math.max(tspanNode.getComputedTextLength(), widestWidth);
              numLines++;
            }
          }
          tspanNode = document.createElementNS(svgUrl, "tspan");
          tspanNode.setAttributeNS(null, "x", x);
          tspanNode.setAttributeNS(null, "dy", i === 0 ? 0 : dy);
          textNode = document.createTextNode(line);
          tspanNode.appendChild(textNode);
          svgTextNode.appendChild(tspanNode);
          if (~dashArray.indexOf(curLineLength - 1)) {
            line = word + "-";
          } else {
            line = word + " ";
          }
          if (i && lastWord) {
            line = lastWord + line;
          }
        } else {
          if (~dashArray.indexOf(curLineLength - 1)) {
            line += word + "-";
          } else {
            line += word + " ";
          }
        }
        tspanNode.firstChild.nodeValue = line;
        computedTextLength = tspanNode.getComputedTextLength();
        if (newlinemode) {
          widestWidth = Math.max(tspanNode.getComputedTextLength(), widestWidth);
        }
        if (!newlinemode) {
          if (i && i === words.length - 1 && maxWidth > 0 && computedTextLength > maxWidth) {
            tempText = tspanNode.firstChild.nodeValue;
            tspanNode.firstChild.nodeValue = tempText.slice(0, tempText.length - words[i].length - 1);
            tspanNode = document.createElementNS(svgUrl, "tspan");
            tspanNode.setAttributeNS(null, "x", x);
            tspanNode.setAttributeNS(null, "dy", dy);
            textNode = document.createTextNode(words[i]);
            tspanNode.appendChild(textNode);
            svgTextNode.appendChild(tspanNode);
            numLines++;
          }
        }
      }
      if (widestWidth === 0) {
        widestWidth = svgTextNode.childNodes[0].getComputedTextLength();
      }
      if (maxWidth > widestWidth) {
        width = maxWidth;
      } else {
        width = widestWidth;
      }
      return {
        lines: numLines,
        width: width,
        textWidth: widestWidth
      };
    };
  });

}).call(this);

/*global define: false */

define('common/views/gradients',[],function() {
  return {
    /**
     * Creates a new radial gradient or updates existing one.
     *
     * @param  {[type]} id
     * @param  {[type]} lightColor
     * @param  {[type]} medColor
     * @param  {[type]} darkColor
     * @param  {[type]} container SVG container which will be used to store gradients definitions.
     * @return {string}           Gradient URL string, e.g. "url(#green-gradient)"
     */
    createRadialGradient: function(id, lightColor, medColor, darkColor, container) {
      var gradientUrl, defs, gradient;
      defs = container.select("defs");
      if (defs.empty()) {
        // Store gradients in 'defs' element.
        defs = container.append("defs");
      }

      gradient = defs.select("#" + id);

      if (gradient.empty()) {
        // Create a new gradient.
        gradient = defs.append("radialGradient").attr("id", id).attr("cx", "50%").attr("cy", "47%").attr("r", "53%").attr("fx", "35%").attr("fy", "30%");
      } else {
        gradient.selectAll("stop").remove()
      }

      gradient.append("stop").attr("stop-color", lightColor).attr("offset", "0%");
      gradient.append("stop").attr("stop-color", medColor).attr("offset", "40%");
      gradient.append("stop").attr("stop-color", darkColor).attr("offset", "80%");
      gradient.append("stop").attr("stop-color", medColor).attr("offset", "100%");

      gradientUrl = "url(#" + id + ")";
      // Store main color (for now - dark color) of the gradient.
      // Useful for radial bonds. Keys are URLs for convenience.
      this.mainColorOfGradient[gradientUrl] = darkColor;
      return gradientUrl;
    },

    /**
     * Hash which defines the main color of a given gradient.
     * Note that for convenience, keys are in forms of URLs (e.g. url(#some-gradient)).
     * e.g. useful for MD2D radial bonds, which can adjust their color to gradient.
     */
    mainColorOfGradient: {},
    /**
     * NOT INTENDED FOR INDEPENDENT USE
     * Resolves mismatched parentheses in a string.
     * Optionally, removes or "peels" the outermost pair of parenthesis
     *
     * @param  {string} string    String to resolve
     * @param  {boolean} peel     Whether or not to "peel" the outermost parenthesis pair.
     * @return {string}           Resolved string
     */
    settleParens: function(string, peel) {
      var end, inparens, firstparen;
      inparens = false;
      string = string.split("")
      for (end = 0; end < string.length; end++) {
        if (inparens) {
          if (string[end] == ")") {
            inparens = false;
          } else if (string[end] == "(") {
            string[firstparen] = " ";
            firstparen = end;
          }
        } else {
          if (string[end] == "(") {
            if (peel) {
              string[end] = " "
              peel = false
            } else {
              inparens = true;
              firstparen = end;
            }
          } else if (string[end] == ")") {
            string[end] = " ";
          }
        }
      }
      return string.join("").replace(/\s+/, " ").trim()
    },
    /**
     * NOT INTENDED FOR INDEPENDENT USE
     * Splits a string by space or comma, keeping parentheses intact
     * Then, parses each "word" for possible meanings
     * Optionally, removes or "peels" the outermost pair of parenthesis before splitting
     *
     * @param  {string} string    String to split
     * @param  {boolean} peel     Whether or not to "peel" the outermost parenthesis pair.
     * @return {Array.<Object>}   Array of "words"
     */
    smartSplit: function(string, peel) {
      var start, end, result, inparens, temp;
      string = this.settleParens(string, peel);
      inparens = false;
      result = [];
      for (end = 1; end < string.length; end++) {
        if (inparens) {
          if (string[end] == ")") {
            inparens = false;
          }
          continue;
        }
        if (string[end] == "," || string[end] == " ") {
          temp = string.slice(start, end).trim()
          if (temp.length > 0) {
            result.push(this.parseWord(temp));
          }
          start = end + 1;
        } else if (string[end] == "(") {
          inparens = true;
        }
      }
      if (end == string.length) {
        result.push(this.parseWord(string.slice(start, end).trim()));
      }
      return result;
    },
    /**
     * NOT INTENDED FOR INDEPENDENT USE
     * Finds all possible meanings of a given word, storing those
     * as properties into a String representation of the word
     *
     * @param  {string} word                      Word to find the meaning of
     * @return {Object.<string,number|boolean>}   Word as a String object and properties set
     */
    parseWord: function(word) {
      var result, temp;
      result = new String(word);
      if (word.search(/(^|[^a-zA-Z])li?n?e?a?r?($|[^a-zA-Z])/) != -1) {
        result.isLinear = true;
      }
      if (word.search(/(^|[^a-zA-Z])ra?d?i?a?l?($|[^a-zA-Z])/) != -1) {
        result.isRadial = true;
      }
      if (word.search(/(^|[^a-zA-Z])gr?a?d?i?e?n?t?($|[^a-zA-Z])/) != -1) {
        result.isGradient = true;
      }
      if (word.search(/(\(|\))/) != -1) {
        result.isParens = true;
        return result;
      }
      temp = parseFloat(word);
      if (!isNaN(temp)) {
        result.asNumber = temp;
      }
      if (word.search(/(^|[^a-zA-Z])de?g?r?e?e?s?($|[^a-zA-Z])/) != -1) {
        result.isDegrees = true;
      }
      if (word.search(/(^|[^a-zA-Z])ri?g?h?t?($|[^a-zA-Z])/) != -1) {
        result.isRight = true;
      }
      if (word.search(/(^|[^a-zA-Z])le?f?t?($|[^a-zA-Z])/) != -1) {
        result.isLeft = true;
      }
      if (word.search(/(^|[^a-zA-Z])(t|u)o?p?($|[^a-zA-Z])/) != -1) {
        result.isTop = true;
      }
      if (word.search(/(^|[^a-zA-Z])bo?t?t?o?m?($|[^a-zA-Z])/) != -1) {
        result.isBottom = true;
      }
      if (word.search(/(^|[^a-zA-Z])re?pe?a?t?($|[^a-zA-Z])/) != -1) {
        result.isRepeat = true;
      }
      if (word.search(/(^|[^a-zA-Z])re?fl?e?c?t?($|[^a-zA-Z])/) != -1) {
        result.isReflect = true;
      }
      return result;
    },
    /**
     * NOT INTENDED FOR INDEPENDENT USE
     * Given an array containing stop objects of color data and optionally offset data,
     * interpolate values for stops without offsets.
     * e.g.
     * [{"color":"red","offset":0},{"color":"yellow"},{"color":"blue","offset":100}]
     * would return
     * [{"color":"red","offset":0},{"color":"yellow","offset":50},{"color":"blue","offset":100}]
     *
     * @param  {Array.<Object<string,Object|string|number>>} stops  Array of objects containing color data and optionally offset data
     * @return {Array.<Object<string,Object|string|number>>}        Array of objects containing both color and offset data
     */
    interpolateStops: function(stops) {
      var i, j, k, start, end, step;
      if (stops.length == 0) {
        return stops;
      }
      if (!("offset" in stops[0])) {
        stops[0].offset = 0;
      }
      if (stops.length == 1) {
        return stops;
      }
      if (!("offset" in stops[stops.length - 1])) {
        stops[stops.length - 1].offset = 100;
      }
      for (i = 2; i < stops.length; i++) {
        if ("offset" in stops[i] && !("offset" in stops[i - 1])) {
          start = 0;
          end = stops[i].offset;
          for (j = i - 2; j >= 0; j--) {
            if ("offset" in stops[j]) {
              start = stops[j].offset;
              break;
            }
          }
          step = (end - start) / (i - j)
          for (var k = j + 1; k < i; k++) {
            stops[k].offset = start + step * (k - j);
          }
        }
      }
      return stops
    },
    /**
     * NOT INTENDED FOR INDEPENDENT USE: use gradients.parse instead
     * Parses and returns any color or gradient that a given string contains
     * as a gradient/color object of the form
     * {"color":"red"}
     * or
     * {"gradient":"linear","direction":0,"stops":[{"color":"red","offset":0}, ... ]}
     *
     * @param  {string} string  String
     * @return {Object}         Gradient/color object
     */
    fullParse: function(string) {
      //Preliminary gradient verification
      string = string.trim().toLowerCase();
      if (string.search(/[^#0-9a-zA-Z-]/) == -1) {
        return {
          "color": string
        };
      }
      var stops, words, direction, linear, radial, i, gradient, remove, x_direction, y_direction, repeat;

      //String preprocessing
      string = string.replace(/[\n\r:;]/g, " ").replace(/\/\*.*\*\/$/, "").replace(/\{\[/, "(").replace(/\}\]/, ")").replace(/[^a-zA-Z0-9%#\-\(\)\., ]*/g, "").replace(/\s*-\s+/g, "-").replace(/\s*\(/g, "(").replace(/\s+/g, " ");
      words = this.smartSplit(string, false);
      gradient = {};

      //Remove any irrelevant parentheses
      for (i = 0; i < words.length; i++) {
        if (words[i].isParens === true && (words[i].isGradient === true || words[i].isLinear === true || words[i].isRadial === true)) {
          words.splice.apply(words, [i, 1].concat(this.smartSplit(words[i], true)))
        }
      }

      //Parse gradient metadata
      direction = false;
      radial = false;
      linear = false;
      repeat = false;
      x_direction = 0;
      y_direction = 0;
      for (i = 0; i < words.length; i++) {
        remove = false
        if (words[i].isLeft === true) {
          x_direction -= 1;
          remove = true;
        } else if (words[i].isRight === true) {
          x_direction += 1;
          remove = true;
        }
        if (words[i].isTop === true) {
          y_direction += 1;
          remove = true;
        } else if (words[i].isBottom === true) {
          y_direction -= 1;
          remove = true;
        }
        if (words[i].isRadial === true && !(words[i].isRight === true)) {
          radial = true;
          remove = true;
        }
        if (words[i].isLinear === true && !(words[i].isLeft === true)) {
          linear = true;
          remove = true;
        }
        if ("asNumber" in words[i]) {
          if (words[i].isDegrees === true || i + 1 < words.length && words[i + 1].isDegrees === true) {
            if (direction === false) {
              direction = words[i].asNumber;
            }
            remove = true;
          }
        }
        if (words[i].isRepeat === true) {
          repeat = "repeat";
          remove = true;
        } else if (words[i].isReflect === true) {
          repeat = "reflect";
          remove = true;
        }
        if (remove || words[i].isDegrees === true || words[i].isGradient === true) {
          words.splice(i, 1);
          i--;
        }
      }
      if (direction === false && (x_direction != 0 || y_direction != 0)) {
        direction = (180 * Math.atan2(y_direction, x_direction) / Math.PI % 360);
      }

      //Parse the stops
      stops = [];
      for (i = 0; i < words.length; i++) {
        if (!("asNumber" in words[i])) {
          if (i + 1 < words.length && ("asNumber" in words[i + 1])) {
            stops.push({
              color: words[i],
              offset: words[i + 1].asNumber
            })
            words.splice(i, 2)
          } else {
            stops.push({
              color: words[i]
            })
            words.splice(i, 1)
          }
          i--
        }
      }
      if (stops.length == 1) {
        return {
          "color": stops[0].color
        };
      }
      if (stops.length == 0) {
        return {
          "color": "none"
        };
      }
      this.interpolateStops(stops);

      //Parse leftover words
      for (i = 0; i < words.length; i++) {
        if (words[i].isNumber === true) {
          if (direction === false) {
            direction = words[i].asNumber;
          }
        }
      }

      //Build and return gradient object
      if (radial && !linear) {
        direction = false
        gradient.gradient = "radial"
      } else {
        if (direction === false) {
          direction = 0
        }
        direction %= 360;
        if (direction < 0) {
          direction += 360;
        }
        gradient.gradient = "linear"
        gradient.direction = direction
      }
      if (repeat !== false) {
        gradient.repeat = repeat
      }
      gradient.stops = stops;
      gradient.standard = this.toStandardForm(gradient);
      return gradient;
    },
    /**
     * Parses and returns any color or gradient that a given string contains
     * as a gradient/color object of the form
     * {"color":"red"}
     * or
     * {"gradient":"linear","direction":0,"stops":[{"color":"red","offset":0}, ... ]}
     *
     * A string given in standard form is faster to parse. An arbitrary string will use gradient.fullParse.
     *
     * @param  {string} string  String
     * @return {Object}         Gradient/color object
     */
    parse: function(string) {
      string = string.trim().toLowerCase().replace(/\s+/g, " ")
      if (string.search(/ /) == -1) {
        return {
          "color": string,
          "standard": string
        };
      }
      var stops, direction, gradient, type, s, offset, restandardize;
      stops = string.split(" ");
      gradient = {}
      type = stops.shift()
      if (type == "linear" || type == "radial") {
        gradient.gradient = type;
      } else {
        return this.fullParse(string);
      }
      if (type == "linear") {
        direction = parseFloat(stops.shift());
        if (isNaN(direction)) {
          return this.fullParse(string);
        }
        direction %= 360;
        if (direction < 0) {
          direction += 360;
        }
        gradient.direction = direction;
        restandardize = true;
      }
      gradient.stops = []
      if (stops.length % 2 == 0) {
        for (s = 0; s < stops.length; s += 2) {
          offset = parseFloat(stops[s + 1])
          if (isNaN(offset)) {
            return this.fullParse(string);
          }
          gradient.stops.push({
            "color": stops[s],
            "offset": offset
          });
        }
      } else {
        return this.fullParse(string);
      }
      if (restandardize) {
        gradient.standard = this.toStandardForm(gradient);
      } else {
        gradient.standard = string;
      }
      return gradient;
    },
    /**
     * Applies a given gradient/color to the background of a given element
     *
     * @param  {Object} color   A gradient/color object created by gradients.parse
     * @param  {Object} element The specified DOM element to apply the background to
     */
    toCSS: function(color, element) {
      var temp, i;
      if ("color" in color) {
        element.style.backgroundColor = color.color.length ? color.color : "inherit"
        element.style.backgroundImage = "none"
      } else {
        temp = (color.gradient == "radial" ? "-webkit-radial-gradient(ellipse," : "-webkit-linear-gradient(" + color.direction + "deg, ");
        for (i = 0; i < color.stops.length; i++) {
          temp += color.stops[i].color + " " + color.stops[i].offset + "%";
          if (i < color.stops.length - 1) {
            temp += ",";
          }
        }
        temp += ")";
        element.style.backgroundColor = "none";
        element.style.backgroundImage = temp;
      }
    },
    /**
     * Converts a given gradient/color to standard form
     *
     * @param  {Object} color A gradient/color object created by gradients.parse
     * @return {string}       The converted standard form string
     */
    toStandardForm: function(color) {
      var temp, i;
      if ("color" in color) {
        color.standard = color.color;
        return color.color;
      } else {
        temp = color.gradient + " " + (color.gradient == "linear" ? color.direction + "deg " : "");
        for (i = 0; i < color.stops.length; i++) {
          temp += color.stops[i].color + " " + color.stops[i].offset + "%";
          if (i < color.stops.length - 1) {
            temp += " ";
          }
        }
        color.standard = temp;
        return temp;
      }
    },
    /**
     * Converts a given gradient/color to an SVG fill, creating a gradient definition if necessary
     *
     * @param  {Object} color    A gradient/color object created by gradients.parse
     * @param  {Object} continer The container to append the gradient definitions to
     * @return {string}          The converted string for use in SVG fill
     */
    toSVG: function(color, container) {
      if (color.color) {
        return color.color;
      }
      var gradientUrl, defs, gradient, id, c, x0, y0, xt, yt, args, stop;
      defs = container.select("defs");
      if (defs.empty()) {
        // Store gradients in "defs" element.
        defs = container.append("defs");
      }

      id = color.standard.replace(/[^a-z0-9]/g, "");
      gradient = defs.select("#" + id);

      if (gradient.empty()) {
        // Create a new gradient.
        if (color.gradient == "linear") {
          xt = -Math.cos(Math.PI * color.direction / 180);
          yt = -Math.sin(Math.PI * color.direction / 180);
          if (Math.abs(yt) > Math.abs(xt)) {
            x0 = xt * Math.abs(50 / yt);
            y0 = (yt < 0 ? -1 : 1) * 50;
          } else {
            x0 = (xt < 0 ? -1 : 1) * 50;
            y0 = yt * Math.abs(50 / xt);
          }
          gradient = defs.append("linearGradient").attr("id", id).attr("x1", (50 + x0) + "%").attr("y1", (50 + y0) + "%").attr("x2", (50 - x0) + "%").attr("y2", (50 - y0) + "%");
        } else {
          gradient = defs.append("radialGradient").attr("id", id).attr("cx", "50%").attr("cy", "50%").attr("r", "50%").attr("fx", "50%").attr("fy", "50%");
        }
      } else {
        // Gradient exists. Using it.
        return "url(#" + id + ")";
      }

      for (c = 0; c < color.stops.length; c++) {
        stop = gradient.append("stop").attr("stop-color", color.stops[c].color).attr("offset", color.stops[c].offset + "%");
        args = color.stops[c].color.split(",");
        if (args.length == 4 && !isNaN(args = parseFloat(args[3]))) {
          stop.attr("stop-opacity", args)
        }
      };

      return "url(#" + id + ")";
    }
  };
});
define('models/md2d/views/images-renderer',['lab.config'],function() {

  var labConfig = require('lab.config');
  var UPPER_LEFT = 0;
  var CENTER = 1;

  return function ImagesRenderer(modelView, model) {
    // A collection of image dimensions for each <image> src that has been seen by the renderer.
    var imgDimBySrc = {};

    // Nested arrays of data about the requested images, ready to be joined to the d3 selection.
    var imageData;

    // In order to center and scale images correctly, we need their actual width and height.
    // However, this information loads asynchronously, so the width and height adjustments may need
    // to happen after we have completed setting up images.
    // Therefore, call this method before creating an <image> element with the given src.
    // It will create an Image object with that src, if we do not already have one, and when the
    // Image finishes loading it will update the width and height of any images in the image
    // container which have the same src.
    function loadImage(src, scale) {
      if (imgDimBySrc[src]) {
        return;
      }

      // Create placeholder, it will be updated when image is loaded.
      imgDimBySrc[src] = {
        width: 0,
        height: 0
      };

      var image = new Image();
      image.src = src;
      image.onload = function() {
        // IE11 workaround - without adding an image to DOM, we can't get its correct dimensions.
        // When image src refers to an SVG image, its width and height will be always reported as 0.
        // See: https://www.pivotaltracker.com/story/show/66376534
        var $image = $(image).appendTo("body");
        var imageDim = imgDimBySrc[src];
        imageDim.width = $image.width() * scale;
        imageDim.height = $image.height() * scale;
        $image.remove();
        // Resize and recenter any instances of this image currently in the DOM.
        d3.select(modelView.node)
          .selectAll('image').each(function() {
            if (d3.select(this).attr('xlink:href') === src) {
              d3.select(this)
                .attr('width', scaleImageLength(imageDim.width))
                .attr('height', scaleImageLength(imageDim.height))
                .attr('transform', transform);
            }
          });

        // This selector-based alternative to the .each() above actually works in Chrome, but other
        // (conforming!) browsers require that 'src' be CSS escaped first
        // (see http://mathiasbynens.be/notes/css-escapes)
        //
        // d3.select(modelView.node)
        //   .selectAll('image[*|href="' + src + '"')
        //   .attr(...)
      };
    }

    // Images prepared for Classic MW have 1 pixel == 0.1 Angstrom (0.01 nm). Therefore, convert
    // image dimension from "Classic MW pixels", to nm, to onscreen pixels.
    function scaleImageLength(l) {
      return modelView.model2px(0.01 * l);
    }

    // Sets 'imageData' to a nested array of information about the requested images which can be
    // joined to the nested d3 selection in setup() in order to append the actual <image> elements.
    function setupImageData() {
      var countsBySrc = [{}, {}];
      imageData = [[], []];

      (model.properties.images || []).forEach(function(desc) {
        var layerIndex = desc.imageLayer === 1 ? 1 : 0;
        var layer = imageData[layerIndex];
        var countBySrc = countsBySrc[layerIndex];
        var src = getImagePath(desc);
        var referencePoint;
        var capturesPointer;
        var x;
        var y;

        if (desc.imageHostType) {
          // imageHostType => image is "attached" to an atom or obstacle, and x and y specify the
          // model coordinates of its center.
          referencePoint = CENTER;
          // Images attached to objects still allow the underlying object to be dragged.
          capturesPointer = false;
        } else {
          // no imageHostType => image is at a fixed coordinate in the model space, and x and y
          // specify the model coordinates of its upper left corner.
          referencePoint = UPPER_LEFT;
          // Unattached images prevent dragging of whatever is beneath them, in the usual way.
          capturesPointer = true;
          // Update x and y now; updateImageCoordinates will be a noop.
          x = modelView.model2px(desc.imageX);
          y = modelView.model2pxInv(desc.imageY);
        }

        loadImage(src, desc.scale);

        layer.push({
          imageDescription: desc,
          src:              src,
          // Use src + srcIndex to create a unique key for images. This allows us to use a
          // conservative data join and thereby avoid having to modify the href of <image> elements.
          // That prevents the browser from issuing unnecessary requests.
          srcIndex:         countBySrc[src] === undefined ? (countBySrc[src] = 0) : ++countBySrc[src],
          x:                x,
          y:                y,
          rotation:         desc.rotation,
          scale:            desc.scale,
          opacity:          desc.opacity,
          referencePoint:   referencePoint,
          capturesPointer:  capturesPointer,
          zOrder:           desc.imageLayerPosition || 0,
          visible:          desc.visible
        });
      });

      // Finally, because imageData for each layer is 1:1 with svg image elements, sort the
      // imageData for each layer by the intended z order of the images.
      imageData.forEach(function(layer) {
        layer.sort(function(a, b) {
          return d3.ascending(a.zOrder, b.zOrder);
        });
      });

      updateImageCoordinates();
    }

    // Update just the x, y of each element in imageData (except for fixed elements)
    function updateImageCoordinates() {
      imageData.forEach(function(layer) {
        layer.forEach(function(datum) {

          var imageDescription = datum.imageDescription;
          var hostType = imageDescription.imageHostType;
          var index    = imageDescription.imageHostIndex;
          var rotation = imageDescription.rotation || 0;
          var atoms;
          var obstacles;
          var radialBond;
          var x, y;

          if (hostType === 'Atom') {
            atoms = model.getAtoms();
            x = atoms[index].x;
            y = atoms[index].y;
          } else if(hostType === 'RectangularObstacle') {
            obstacles = model.get_obstacles();
            x = obstacles.x[index] + obstacles.width[index] / 2;
            y = obstacles.y[index] + obstacles.height[index] / 2;
          } else if(hostType === 'RadialBond'){
            radialBond = model.getRadialBonds()[index];
            x = (radialBond.x1 + radialBond.x2) / 2;
            y = (radialBond.y1 + radialBond.y2) / 2;
            rotation += 180 * Math.atan2(radialBond.y1 - radialBond.y2, radialBond.x1 - radialBond.x2) / Math.PI;
          }
          if (x !== undefined) {
            datum.x = modelView.model2px(x);
            datum.y = modelView.model2pxInv(y);
          }
          datum.rotation = rotation;
        });
      });
    }

    function getImagePath(imageDescription) {
      var imageMapping = model.properties.imageMapping;
      var basePath = "";

      if (model.properties.imagePath) {
        basePath = labConfig.modelsRootUrl + model.properties.imagePath;
      } else if (modelView.url) {
        basePath = labConfig.modelsRootUrl + modelView.url.slice(0, modelView.url.lastIndexOf("/") + 1);
      }

      return basePath + (imageMapping[imageDescription.imageUri] || imageDescription.imageUri);
    }

    // Rotate and translate the image according to x, y, width, height, and rotation
    function transform(d) {
      var dim = imgDimBySrc[d.src];
      var halfWidth  = scaleImageLength(dim.width  / 2);
      var halfHeight = scaleImageLength(dim.height / 2);
      var x;
      var y;
      var t;

      if (d.referencePoint === UPPER_LEFT) {
        x = d.x;
        y = d.y;
      } else {
        // center of object should be at (d.x, d.y), so upper left corner needs to be translated by
        // a half-width and a half-length less.
        x = d.x - halfWidth;
        y = d.y - halfHeight;
      }

      if (d.rotation === 0) {
        t = ["translate(", x, ", ", y, ")"];
      } else {
        // invert sign of rotation so that rotations occur in standard (counterclockwise) direction
        t = ["translate(", x, ", ", y, ") rotate(", -d.rotation, ",", halfWidth, ",", halfHeight, ")"];
      }
      return t.join('');
    }

    // Called whenever the set of images or their ordering change in some way.
    function setup() {
      setupImageData();

      d3.select(modelView.node).selectAll('.image-container')
        .data(imageData)
        .each(function(data) {
          var images = d3.select(this)
            .selectAll('image')
            .data(data, function (d) { return d.src + '-' + d.srcIndex; });

          images.enter()
            .append('image')
            .attr('xlink:href', function(d) { return d.src; });

          images.attr({
            height:           function (d) { return scaleImageLength(imgDimBySrc[d.src].height); },
            width:            function (d) { return scaleImageLength(imgDimBySrc[d.src].width); },
            opacity:          function (d) { return d.opacity; },
            'pointer-events': function(d) { return d.capturesPointer ? 'auto' : 'none'; },
            transform:        transform
          }).style('display', function(d) { return d.visible ? '' : 'none'; });

          images.exit().remove();
        });
    }

    // Called for every tick; just updates image coordinates.
    function update() {
      updateImageCoordinates();

      // No need for a nested selection as all images are already bound to the correct data items.
      // Just need to let d3 know it should reapply x, y because the data items have been updated.
      d3.select(modelView.node).selectAll('.image-container image')
        .attr('transform', transform);
     }

    function bindModel(_model) {
      model = _model;
    }

    return {
      setup: setup,
      update: update,
      bindModel: bindModel
    };
  };
});

/*global $, define: false, d3: false, AUTHORING: false */
/*jshint loopfunc: true */

// ------------------------------------------------------------
//
//   MD2D View Renderer
//
// ------------------------------------------------------------
define('models/md2d/views/renderer',['require','lab.config','common/console','common/benchmark/benchmark','models/md2d/views/atoms-renderer','models/md2d/views/bonds-renderer','models/md2d/views/vectors-renderer','models/md2d/views/field-renderer','./vdw-lines-renderer','models/md2d/views/genetic-renderer','cs!common/layout/wrap-svg-text','common/views/gradients','common/views/color','./images-renderer'],function(require) {
  // Dependencies.
  var labConfig         = require('lab.config'),
    console             = require('common/console'),
    benchmark           = require('common/benchmark/benchmark'),
    AtomsRenderer       = require('models/md2d/views/atoms-renderer'),
    BondsRenderer       = require('models/md2d/views/bonds-renderer'),
    VectorsRenderer     = require('models/md2d/views/vectors-renderer'),
    FieldRenderer       = require('models/md2d/views/field-renderer'),
    VdwLinesRenderer    = require('./vdw-lines-renderer'),
    GeneticRenderer     = require('models/md2d/views/genetic-renderer'),
    wrapSVGText         = require('cs!common/layout/wrap-svg-text'),
    gradients           = require('common/views/gradients'),
    contrastingColor    = require('common/views/color').contrastingColor,
    ImagesRenderer      = require('./images-renderer'),

    // Constants used by photon renderer:
    // Reference implementation:
    // https://github.com/concord-consortium/mw/blob/3c54e37cbfe98b8d3c6f9e5397eda4308dfafecb/src/org/concord/mw2d/models/Photon.java#L32-L35
    MIN_VISIBLE_FREQ = 5,
    MAX_VISIBLE_FREQ = 12,
    PHOTON_COLORS = [[255, 0, 0], [255, 200, 0], [255, 255, 0], [0, 255, 0], [0, 255, 255], [0, 0, 255], [139, 0, 255]];

  return function MD2DView(modelView, model) {
    // Public API object to be returned.
    var api = {},

      i18n = modelView.i18n,

      // Allows us to defer running actual renderer setup until layout system has determined oursize.
      isSetup = false,

      // The model function getAtoms() returns a 2 dimensional array
      // of particle indices and properties that is updated every model tick.
      // This array is not garbage-collected so the view can be assured that
      // the latest modelAtoms will be in this array when the view is executing
      modelAtoms,
      modelElectricField,
      modelElements,
      modelWidth,
      modelHeight,
      aspectRatio,

      // Basic scaling functions for position, it transforms model units to "pixels".
      // Use it for positions of objects rendered inside the view.
      model2px,

      // Inverted scaling function for position transforming model units to "pixels".
      // Use it for Y coordinates, as Y axis in model coordinate system increases
      // from bottom to top, while but SVG has increases from top to bottom
      model2pxInv,

      // "Viewports" - SVG elements whose viewbox is automatically adjusted appropriately by the
      // container (called modelView here although it's a generic container, *not* the modelView)
      belowAtomsViewport = modelView.appendViewport().classed("below-atoms", true),

      // "Containers" - G elements used to position layers of the final visualization.
      shapeContainerBelow  = belowAtomsViewport.append("g").attr("class", "shape-container-below"),
      imageContainerBelow  = belowAtomsViewport.append("g").attr("class", "image-container image-container-below"),
      textContainerBelow   = belowAtomsViewport.append("g").attr("class", "text-container-below"),

      // TODO: remove it, as well as legacy code responsible for SVG atoms rendering.
      atomsViewport  = modelView.appendViewport().classed("atoms", true),
      atomsContainer = atomsViewport.append("g").attr("class", "atoms-container"),

      vectorsBelowPixi = modelView.appendPixiViewport(),
      vdwLinesPixi = modelView.appendPixiViewport(),
      bondsPixi = modelView.appendPixiViewport(),
      atomsPixi = modelView.appendPixiViewport(),
      vectorsAbovePixi = modelView.appendPixiViewport(),

      aboveAtomsViewport = modelView.appendViewport().classed("above-atoms", true),
      shapeContainerTop  = aboveAtomsViewport.append("g").attr("class", "shape-container-top"),
      lineContainerTop   = aboveAtomsViewport.append("g").attr("class", "line-container-top"),
      imageContainerTop  = aboveAtomsViewport.append("g").attr("class", "image-container image-container-top"),
      textContainerTop   = aboveAtomsViewport.append("g").attr("class", "text-container-top"),

      iconContainer = modelView.foregroundContainer.append("g").attr("class", "icon-container"),

      // Renderers specific for MD2D
      // TODO: try to create new renderers in separate files for clarity and easier testing.
      atomsRenderer = new AtomsRenderer(modelView, model, atomsPixi.pixiContainer, atomsPixi.canvas),
      bondsRenderer = new BondsRenderer(modelView, model, bondsPixi.pixiContainer, atomsRenderer),
      vdwLinesRenderer = new VdwLinesRenderer(modelView, model, vdwLinesPixi.pixiContainer),
      velocityVectorsRenderer = new VectorsRenderer(vectorsAbovePixi.pixiContainer, {
        get show() { return model.get("showVelocityVectors"); },
        get length() { return model.get("velocityVectors").length; },
        get width() { return model.get("velocityVectors").width; },
        get color() { return model.get("velocityVectors").color; },
        get dirOnly() { return false; },
        get count() { return modelAtoms.length; },
        x: function (i) { return modelAtoms[i].x; },
        y: function (i) { return modelAtoms[i].y; },
        vx: function (i) { return modelAtoms[i].vx * 100; },
        vy: function (i) { return modelAtoms[i].vy * 100; },
        m2px: modelView.model2canvas,
        m2pxInv: modelView.model2canvasInv
      }),
      forceVectorsRenderer = new VectorsRenderer(vectorsAbovePixi.pixiContainer, {
        get show() { return model.get("showForceVectors"); },
        get length() { return model.get("forceVectors").length; },
        get width() { return model.get("forceVectors").width; },
        get color() { return model.get("forceVectors").color; },
        get dirOnly() { return model.get("forceVectorsDirectionOnly"); },
        get count() { return modelAtoms.length; },
        x: function (i) { return modelAtoms[i].x; },
        y: function (i) { return modelAtoms[i].y; },
        vx: function (i) { var a = modelAtoms[i]; return a.ax * a.mass * 100; },
        vy: function (i) { var a = modelAtoms[i]; return a.ay * a.mass * 100; },
        m2px: modelView.model2canvas,
        m2pxInv: modelView.model2canvasInv
      }),
      electricFieldRenderer = new FieldRenderer(vectorsBelowPixi.pixiContainer, {
        get show() { return model.get("showElectricField"); },
        get length() { return 0.8 * model.get("width") / model.get("electricFieldDensity"); },
        get color() {
          var c = model.get("electricFieldColor");
          return c !== "auto" ? c : contrastingColor(model.get("backgroundColor"));
        },
        get count() { return modelElectricField.length; },
        x: function (i) { return modelElectricField[i].x; },
        y: function (i) { return modelElectricField[i].y; },
        vx: function (i) { return modelElectricField[i].fx; },
        vy: function (i) { return modelElectricField[i].fy; },
        alpha: function (i) {
          var d = modelElectricField[i];
          return Math.min(1, Math.pow(d.fx * d.fx + d.fy * d.fy, 0.2) * 0.3);
        },
        m2px: modelView.model2canvas,
        m2pxInv: modelView.model2canvasInv
      }),
      imagesRenderer = new ImagesRenderer(modelView, model),
      geneticRenderer,

      arrowHeadsCache,
      fontSizeInPixels,
      obstacle,
      obstacles,
      mockObstaclesArray = [],
      shapes,
      shapeTop,
      shapeBelow,
      mockShapesTop = [],
      mockShapesBelow = [],
      lines,
      lineTop,
      mockLinesTop = [],
      useQuantumDynamics,
      forceVectorColor,
      forceVectorWidth,
      textBoxes,
      // Flag indicating whether there are some movable text boxes or not (e.g. attached to an atom
      // or obstacle). Used for optimization.
      onlyStaticTextBoxes = true,
      drawAtomTrace,
      atomTraceId,
      atomTraceColor,
      atomTrace,
      atomTracePath,

      browser = benchmark.what_browser(),

      // pre-calculations
      halfPi = Math.PI / 2,

      // this is a hack put in place to temporarily deal with a IE 10 bug which
      // does not update line markers when svg lines are moved
      // see https://connect.microsoft.com/IE/feedback/details/781964/
      hideLineMarkers = browser.browser === "MSIE" && Number(browser.version) >= 10;

    function createCustomArrowHead(i, path, start) {
      if(!path || path === "none"){
        return "none";
      }

      var id = "arrowhead-path" + path.toLowerCase().replace(/[^a-z0-9]/g, '') +
               (start ? "start" : "") + lines.lineColor[i].toLowerCase().replace(/[^a-z0-9]/g, '');

      if (!arrowHeadsCache[id]) {
        var defs, arrowHead;
        defs = atomsContainer.select("defs");
        if (defs.empty()) {
          defs = atomsContainer.append("defs");
        }
        arrowHead = defs.select("#" + id);
        arrowHead = atomsContainer.append("marker")
          .attr("id", id)
          .attr("class", "custom-arrow-head")
          .attr("viewBox", "0 0 10 10")
          .attr("refX", "5")
          .attr("refY", "5")
          .attr("markerUnits", "strokeWidth")
          .attr("markerWidth", "4")
          .attr("markerHeight", "4")
          .attr("orient", "auto");
        arrowHead.append("path")
          .attr("d", path)
          .attr("fill", lines.lineColor[i])
          .attr("transform", start ? "translate(10, 10) rotate(180)" : "");
        arrowHeadsCache[id] = true;
      }
      return "url(#" + id + ")";
    }

    function createVectorArrowHeads(color, name) {
      var defs,
        id = "Triangle-" + name,
        arrowHead;
      defs = atomsContainer.select("defs");
      if (defs.empty()) {
        defs = atomsContainer.append("defs");
      }
      arrowHead = defs.select("#" + id).remove();
      arrowHead = defs.append("marker")
        .attr("id", "Triangle-" + name)
        .attr("viewBox", "0 0 10 10")
        .attr("refX", "0")
        .attr("refY", "5")
        .attr("markerUnits", "strokeWidth")
        .attr("markerWidth", "4")
        .attr("markerHeight", "3")
        .attr("orient", "auto")
        .attr("stroke", color)
        .attr("fill", color);
      arrowHead.append("path")
        .attr("d", "M 0 0 L 10 5 L 0 10 z");
    }

    function createExcitationGlow() {
      var defs,
        glow;

      defs = atomsContainer.select("defs");
      if (defs.empty()) {
        defs = atomsContainer.append("defs");
      }
      glow = defs.select("#glow");
      if (glow.empty()) {
        glow = defs.append("filter")
          .attr("id", "glow")
          .attr("x", "-2")
          .attr("y", "-2")
          .attr("width", "800%")
          .attr("height", "800%");
        glow.append("feMorphology")
          .attr("result", "bigger")
          .attr("in", "SourceGraphic")
          .attr("operator", "dilate")
          .attr("radius", "6");
        glow.append("feGaussianBlur")
          .attr("result", "blurOut")
          .attr("in", "bigger")
          .attr("stdDeviation", "10");
        glow.append("feBlend")
          .attr("in", "SourceGraphic")
          .attr("in2", "blurOut")
          .attr("mode", "normal");
      }
    }

    // Create key images which can be shown in the
    // upper left corner in different situations.
    // IMPORTANT: The height attribute must be set,
    // it will allow to properly calculate images
    // placement in drawSymbolImages() function.
    function createSymbolImages() {
      var xMargin = "1%",
        fSize = Math.max(fontSizeInPixels, 12);

      // only add these images if they don't already exist
      if (iconContainer.select("#heat-bath").empty()) {
        // Heat bath key image.
        iconContainer.append("image")
          .attr({
            "id": "heat-bath",
            "x": xMargin,
            "width": fSize * 2,
            "height": fSize * 2,
            "preserveAspectRatio": "xMinYMin",
            // base64 of lab/resources/upstatement/heatbath.svg
            "xlink:href": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNC4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDQzMzYzKSAgLS0+DQo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9Ijk2LjIzNnB4IiBoZWlnaHQ9IjUzLjMyMXB4IiB2aWV3Qm94PSIxLjc2NCAxMy44MSA5Ni4yMzYgNTMuMzIxIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDEuNzY0IDEzLjgxIDk2LjIzNiA1My4zMjEiDQoJIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGxpbmVhckdyYWRpZW50IGlkPSJTVkdJRF8xXyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSItNjIyIiB5MT0iOTQ1LjU2MSIgeDI9Ii02MjIiIHkyPSI5MjYuNDM5OSI+DQoJPHN0b3AgIG9mZnNldD0iMCIgc3R5bGU9InN0b3AtY29sb3I6IzU1NTU1NSIvPg0KCTxzdG9wICBvZmZzZXQ9IjEiIHN0eWxlPSJzdG9wLWNvbG9yOiNGRkZGRkYiLz4NCjwvbGluZWFyR3JhZGllbnQ+DQo8cGF0aCBmaWxsPSIjRTZFN0U4IiBzdHJva2U9InVybCgjU1ZHSURfMV8pIiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBkPSJNNzEuMzMsMTQuMzg0DQoJYy04Ljk0MiwwLTE2LjgyNyw0LjQ1OS0yMS41OTYsMTEuMjY1Yy00Ljc3LTYuODAzLTEyLjY1NS0xMS4yNjUtMjEuNTk4LTExLjI2NWMtMTQuNTY0LDAtMjYuMzczLDExLjgwNi0yNi4zNzMsMjYuMzczDQoJYzAsMTQuNTY4LDExLjgwNSwyNi4zNzQsMjYuMzczLDI2LjM3NGM4Ljk0MiwwLDE2LjgyOC00LjQ2LDIxLjU5OC0xMS4yNjZjNC43NjksNi44MDIsMTIuNjUzLDExLjI2NiwyMS41OTYsMTEuMjY2DQoJYzE0LjU2NiwwLDI2LjM3Mi0xMS44MDYsMjYuMzcyLTI2LjM3NEM5Ny43MDIsMjYuMTksODUuODk2LDE0LjM4NCw3MS4zMywxNC4zODR6Ii8+DQo8cGF0aCBmaWxsPSIjNTU1NTU1IiBkPSJNMzEuNDQ4LDQ3LjcxOVYyNy4xMzNjMC0xLjE2LTAuOTQxLTIuMS0yLjEwMi0yLjFjLTEuMTU3LDAtMi4wOTUsMC45NC0yLjA5NSwyLjF2MjAuNTg2DQoJYy0xLjM4NSwwLjc1Mi0yLjMzOCwyLjE5Ni0yLjMzOCwzLjg4MWMwLDIuNDQ3LDEuOTg2LDQuNDI4LDQuNDMzLDQuNDI4YzIuNDUxLDAsNC40MzQtMS45OCw0LjQzNC00LjQyOA0KCUMzMy43ODMsNDkuOTE2LDMyLjgzLDQ4LjQ3MSwzMS40NDgsNDcuNzE5eiIvPg0KPHBhdGggZmlsbD0iIzU1NTU1NSIgZD0iTTgwLjIwOSwzNy42NjN2LTMuOTc0YzAtNC43NzUtMy44ODItOC42NTktOC42NTMtOC42NTlTNjIuOSwyOC45MTUsNjIuOSwzMy42ODl2My45NzRoLTIuMDY3djE1Ljk3M2gyMS43Mg0KCVYzNy42NjNIODAuMjA5eiBNNjYuMjM5LDMzLjY4OWMwLTIuOTMsMi4zODQtNS4zMTYsNS4zMTMtNS4zMTZjMi45MzEsMCw1LjMxNiwyLjM4Niw1LjMxNiw1LjMxNnYzLjk3NGgtMTAuNjNWMzMuNjg5eiIvPg0KPC9zdmc+DQo=",
            "class": "opaque-on-hover"
          })
          .append("title")
          .text(i18n.t("md2d.heatbath_icon_tooltip"));
      }
      if (iconContainer.select("#ke-gradient").empty()) {
        // Kinetic Energy Shading gradient image.
        iconContainer.append("image")
          .attr({
            "id": "ke-gradient",
            "x": "0",
            "width": fSize * 2.2,
            "height": fSize * 6,
            "preserveAspectRatio": "xMinYMin",
            // base64 of lab/resources/upstatement/ke-gradient.svg
            "xlink:href": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNC4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDQzMzYzKSAgLS0+DQo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjM5cHgiIGhlaWdodD0iMTExcHgiIHZpZXdCb3g9IjI1LjUgNC4wOTMgMzkgMTExIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDI1LjUgNC4wOTMgMzkgMTExIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGw9IiNFNkU3RTgiIGQ9Ik02MC45MTUsMTA2LjAzOGMwLDYuODEtMS4wNTMsNy45Mi03LjY4LDcuOTJsLTE0LjQ0NSwwLjI3M2MtNi42MjcsMC03LjYyNS0xLjM4NC03LjYyNS04LjE5M1YxMy41NjMNCgljMC02LjgxLDAuOTk4LTguMjIsNy42MjUtOC4yMmgxNC40NDVjNi42MjcsMCw3LjY4LDEuNDEsNy42OCw4LjIyVjEwNi4wMzh6Ii8+DQo8bGluZWFyR3JhZGllbnQgaWQ9IlNWR0lEXzFfIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9Ijk4Ni4wNDQ5IiB5MT0iMTI5MC41MjY2IiB4Mj0iOTg2LjA0NDkiIHkyPSIxMjI2LjEzMjEiPg0KCTxzdG9wICBvZmZzZXQ9IjAiIHN0eWxlPSJzdG9wLWNvbG9yOiNBN0E5QUMiLz4NCgk8c3RvcCAgb2Zmc2V0PSIxIiBzdHlsZT0ic3RvcC1jb2xvcjojRkZGRkZGIi8+DQo8L2xpbmVhckdyYWRpZW50Pg0KPHBhdGggZmlsbD0iI0U2RTdFOCIgc3Ryb2tlPSJ1cmwoI1NWR0lEXzFfKSIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgZD0iTTM2Ljk3MSwyNC43NjMNCgljMC0yLjAwNSwxLjYyNC0zLjYyOCwzLjYyOC0zLjYyOGgxMC45MzdjMi4wMDQsMCwzLjYyOCwxLjYyMiwzLjYyOCwzLjYyOHY2OC40NThjMCwyLjAwMy0xLjYyNCwzLjYyOS0zLjYyOCwzLjYyOUg0MC41OTkNCgljLTIuMDA0LDAtMy42MjgtMS42MjUtMy42MjgtMy42MjlWMjQuNzYzeiIvPg0KPGxpbmVhckdyYWRpZW50IGlkPSJTVkdJRF8yXyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIxMjQ1LjU4ODkiIHkxPSI4OTAuMDQ1MiIgeDI9IjEyNDUuNTg4OSIgeTI9Ijk2Mi4yNiIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgtMSAwIDAgMSAxMjkxLjYyODkgLTg2Ny4yMzgzKSI+DQoJPHN0b3AgIG9mZnNldD0iMC4wMDQzIiBzdHlsZT0ic3RvcC1jb2xvcjojRUY0MTM2Ii8+DQoJPHN0b3AgIG9mZnNldD0iMC4xNjUzIiBzdHlsZT0ic3RvcC1jb2xvcjojRjE2MDRCIi8+DQoJPHN0b3AgIG9mZnNldD0iMC41MTg5IiBzdHlsZT0ic3RvcC1jb2xvcjojRjc5RTg1Ii8+DQoJPHN0b3AgIG9mZnNldD0iMSIgc3R5bGU9InN0b3AtY29sb3I6I0ZGRkZGRiIvPg0KPC9saW5lYXJHcmFkaWVudD4NCjxwYXRoIGZpbGw9InVybCgjU1ZHSURfMl8pIiBkPSJNNDIuODI3LDk1LjAyM2MtMi4yMTUsMC00LjAwOS0xLjc5Ni00LjAwOS00LjAxMVYyNi44OTNjMC0yLjI1NywxLjgyOC00LjA4Niw0LjA4NS00LjA4Nmg2LjI3NA0KCWMyLjI1NywwLDQuMDg2LDEuODI5LDQuMDg2LDQuMDg2djY0LjExOWMwLDIuMjE1LTEuNzk1LDQuMDExLTQuMDExLDQuMDExSDQyLjgyN3oiLz4NCjxyZWN0IHg9IjE0LjM5NyIgeT0iOS42MDciIGZpbGw9Im5vbmUiIHdpZHRoPSI2MS4zNiIgaGVpZ2h0PSIxNC44MTIiLz4NCjx0ZXh0IHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIDEgMzQuNjA2IDE3LjAxMjkpIiBmb250LWZhbWlseT0iJ0FyaWFsTVQnIiBmb250LXNpemU9IjkuNDc5OCI+SElHSDwvdGV4dD4NCjxyZWN0IHg9IjE0LjM5NyIgeT0iMTAzLjAzMyIgZmlsbD0ibm9uZSIgd2lkdGg9IjYxLjM2IiBoZWlnaHQ9IjE0LjgxMiIvPg0KPHRleHQgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgMSAzNS4yOTMgMTA4LjY2OTIpIiBmb250LWZhbWlseT0iJ0FyaWFsTVQnIiBmb250LXNpemU9IjkuNDc5OCI+TE9XPC90ZXh0Pg0KPC9zdmc+DQo=",
            "class": "opaque-on-hover"
          })
          .append("title")
          .text(i18n.t("md2d.ke_icon_tooltip"));
      }
    }

    // Draw key images in the upper left corner.
    // Place them in one row, dynamically calculate
    // y position.

    function drawSymbolImages() {
      var heatBath = model.get('temperatureControl'),
        keShadingMode = model.get('keShading'),
        imageSelect, imageHeight,
        // Variables used for calculating proper y positions.
        yPos = 0,
        yMargin = 1,
        fSize = Math.max(fontSizeInPixels, 12);

      // Heat bath symbol.
      if (heatBath) {
        yPos += yMargin;
        imageSelect = iconContainer.select("#heat-bath")
          .attr({
            "y": yPos,
            "width": fSize * 2,
            "height": fSize * 2
          })
          .style("display", "");

        imageHeight = parseInt(imageSelect.attr("height"), 10);
        yPos += imageHeight;
      } else {
        iconContainer.select("#heat-bath").style("display", "none");
      }

      // Kinetic Energy shading gradient.
      // Put it under heat bath symbol.
      if (keShadingMode) {
        yPos += yMargin;
        iconContainer.select("#ke-gradient")
          .attr({
            "y": yPos,
            "width": fSize * 2.2,
            "height": fSize * 6
          })
          .style("display", "");
      } else {
        iconContainer.select("#ke-gradient").style("display", "none");
      }
    }

    function atomTraceEnter() {
      atomTrace.enter().append("path")
        .attr({
          "class": "atomTrace",
          "d": getAtomTracePath,
          "stroke-width": model2px(0.01),
          "stroke": atomTraceColor,
          "fill": "none",
          "stroke-dasharray": "6, 6"
        });
    }

    function obstacleEnter() {
      var obstacleGroup = obstacle.enter().append("g");

      obstacleGroup
        .attr("class", "obstacle")
        .attr("transform",
          function(d, i) {
            return "translate(" + model2px(obstacles.x[i]) + " " + model2pxInv(obstacles.y[i] + obstacles.height[i]) + ")";
          }
      );
      obstacleGroup.append("rect")
        .attr({
          "class": "obstacle-shape",
          "x": 0,
          "y": 0,
          "width": function(d, i) {
            return model2px(obstacles.width[i]);
          },
          "height": function(d, i) {
            return model2px(obstacles.height[i]);
          },
          "fill": function(d, i) {
            return obstacles.visible[i] ? gradients.toSVG(gradients.parse(obstacles.color[i]), atomsContainer) : "transparent";
          },
          "stroke-width": function(d, i) {
            return obstacles.visible[i] ? 0.2 : 0.0;
          },
          "stroke": function(d, i) {
            return obstacles.visible[i] ? gradients.toSVG(gradients.parse(obstacles.color[i]), atomsContainer) : "transparent";
          }
        });

      // Append external force markers.
      obstacleGroup.each(function(d, i) {
        // Fast path
        if ( ! obstacles.displayExternalAcceleration[i] ||
             ( ! obstacles.externalAx[i] && ! obstacles.externalAy[i]) ) {
          return;
        }

        // Note that arrows indicating obstacle external force use
        // the same options for styling like arrows indicating atom force.
        // Only their length is fixed.
        var obstacleGroupEl = d3.select(this),
          obsHeight = obstacles.height[i],
          obsWidth = obstacles.width[i],
          obsAx = obstacles.externalAx[i],
          obsAy = obstacles.externalAy[i],
          // Use fixed length of force vectors (in nm).
          vecLen = 0.06,
          space = 0.06,
          step, coords;

        // Set arrows indicating horizontal force.
        if (obsAx) {
          // Make sure that arrows keep constant distance between both ends of an obstacle.
          step = (obsHeight - 2 * space) / Math.round((obsHeight - 2 * space) / 0.2);
          coords = d3.range(space, obsHeight, step);
          obstacleGroupEl.selectAll("path.obstacle-force-hor").data(coords).enter().append("path")
            .attr({
              "class": "obstacle-force-hor",
              "d": function(d) {
                if (obsAx < 0)
                  return "M " + model2px(obsWidth + vecLen + space) +
                    "," + model2px(d) +
                    " L " + model2px(obsWidth + space) +
                    "," + model2px(d);
                else
                  return "M " + model2px(-vecLen - space) +
                    "," + model2px(d) +
                    " L " + model2px(-space) +
                    "," + model2px(d);
              }
            });
        }
        // Later set arrows indicating vertical force.
        if (obsAy) {
          // Make sure that arrows keep constant distance between both ends of an obstacle.
          step = (obsWidth - 2 * space) / Math.round((obsWidth - 2 * space) / 0.2);
          coords = d3.range(space, obsWidth, step);
          obstacleGroupEl.selectAll("path.obstacle-force-vert").data(coords).enter().append("path")
            .attr({
              "class": "obstacle-force-vert",
              "d": function(d) {
                if (obsAy < 0)
                  return "M " + model2px(d) +
                    "," + model2px(-vecLen - space) +
                    " L " + model2px(d) +
                    "," + model2px(-space);
                else
                  return "M " + model2px(d) +
                    "," + model2px(obsHeight + vecLen + space) +
                    " L " + model2px(d) +
                    "," + model2px(obsHeight + space);
              }
            });
        }
        // Finally, set common attributes and stying for both vertical and horizontal forces.
        obstacleGroupEl.selectAll("path.obstacle-force-hor, path.obstacle-force-vert")
          .attr({
            "marker-end": hideLineMarkers ? "" : "url(#Triangle-force)",
            "stroke-width": model2px(forceVectorWidth),
            "stroke": forceVectorColor,
            "fill": "none"
          });
      });
    }


    function shapeEnter() {
      var layers = [shapeTop, shapeBelow],
        i;
      for (i = 0; i < layers.length; i++) {
        var shapeGroup = layers[i].enter().append("g");
        shapeGroup.attr("class", "shape").attr("transform", function(d) {
          return "translate(" + model2px(shapes.x[d.index]) + " " + model2pxInv(shapes.y[d.index] + shapes.height[d.index]) + ")";
        });
        shapeGroup.append("rect").attr({
          "class": "shape-rectangle",
          "x": 0,
          "y": 0,
          "width": function(d) {
            return model2px(shapes.width[d.index]);
          },
          "height": function(d) {
            return model2px(shapes.height[d.index]);
          },
          "fill": function(d) {
            return shapes.visible[d.index] && shapes.type[d.index] === 'rectangle' ? gradients.toSVG(gradients.parse(shapes.color[d.index]), atomsContainer) : "transparent";
          },
          "stroke-width": function(d) {
            return shapes.lineWeight[d.index];
          },
          "stroke-dasharray": function(d) {
            return shapes.lineDashes[d.index];
          },
          "stroke": function(d) {
            return shapes.visible[d.index] && shapes.type[d.index] === 'rectangle' ? shapes.lineColor[d.index] : "transparent";
          }
        });
        shapeGroup.append("ellipse").attr({
          "class": "shape-ellipse",
          "cx": function(d) {
            return model2px(shapes.width[d.index]) / 2;
          },
          "cy": function(d) {
            return model2px(shapes.height[d.index]) / 2;
          },
          "rx": function(d) {
            return model2px(shapes.width[d.index]) / 2;
          },
          "ry": function(d) {
            return model2px(shapes.height[d.index]) / 2;
          },
          "fill": function(d) {
            return shapes.visible[d.index] && shapes.type[d.index] === 'ellipse' ? gradients.toSVG(gradients.parse(shapes.color[d.index]), atomsContainer) : "transparent";
          },
          "stroke-width": function(d) {
            return shapes.lineWeight[d.index];
          },
          "stroke-dasharray": function(d) {
            return shapes.lineDashes[d.index];
          },
          "stroke": function(d) {
            return shapes.visible[d.index] && shapes.type[d.index] === 'ellipse' ? shapes.lineColor[d.index] : "transparent";
          }
        });
      }
    }

    function getTextBoxCoords(d) {
      var x, y, hostX, hostY, textX, textY, frameX, frameY, calloutX, calloutY,
        pixelScale = model2px(d.fontSize);

      x = d.x;
      y = d.y;

      if (d.hostType) {
        // Mark that at least one text box has a host - it means that we have to update text boxes
        // each tick. Otherwise, we can follow a fast path and don't update them after creation.
        onlyStaticTextBoxes = false;
        if (d.hostType === "Atom") {
          hostX = modelAtoms[d.hostIndex].x;
          hostY = modelAtoms[d.hostIndex].y;
        } else {
          hostX = obstacles.x[d.hostIndex] + (obstacles.width[d.hostIndex] / 2);
          hostY = obstacles.y[d.hostIndex] + (obstacles.height[d.hostIndex] / 2);
        }
      }

      if (d.hostType && !d.calloutPoint) {
        x = hostX;
        y = hostY;
      }

      if (d.calloutPoint) {
        if (!d.hostType) {
          calloutX = d.calloutPoint[0];
          calloutY = d.calloutPoint[1];
        } else {
          calloutX = hostX;
          calloutY = hostY;
        }
      }

      frameX = model2px(x);
      frameY = model2pxInv(y);

      textX = frameX + pixelScale * 0.75;
      textY = frameY + pixelScale * 1.2;

      calloutX = model2px(calloutX);
      calloutY = model2pxInv(calloutY);

      return [textX, textY, frameX, frameY, calloutX, calloutY];
    }

    function getCalloutPath(location, frame, fullWidth, fullHeight, fontSize) {
      var calloutLocation = [
        parseFloat(location[0]),
        parseFloat(location[1])
      ],
        center = [
          parseFloat(frame.getAttribute("x")) + (fullWidth / 2),
          parseFloat(frame.getAttribute("y")) + (fullHeight / 2)
        ],
        angle = halfPi - Math.atan((calloutLocation[0] - center[0]) / (calloutLocation[1] - center[1])),
        baseSize = Math.min(fontSize, fullHeight / 2),

        dcx = Math.sin(angle) * baseSize,
        dcy = Math.cos(angle) * baseSize;

      return (center[0] + dcx) + ", " + (center[1] - dcy) + " " + (center[0] - dcx) + ", " + (center[1] + dcy) + " " + calloutLocation;
    }

    function updateTextBoxes() {
      var layers = [textContainerTop, textContainerBelow],
        updateText;

      updateText = function(layerNum) {
        var layer = layers[layerNum - 1],
          layerTextBoxes = textBoxes.filter(function(t) {
            return t.layer === layerNum;
          });

        layer.selectAll("g.textBoxWrapper rect")
          .data(layerTextBoxes.filter(function(d) {
            return d.frame;
          }))
          .attr({
            "x": function(d) {
              return getTextBoxCoords(d)[2];
            },
            "y": function(d) {
              return getTextBoxCoords(d)[3];
            },
            "transform": function(d) {
              var rotate = d.rotate,
                pos = getTextBoxCoords(d);
              return "rotate(" + rotate + " " + pos[0] + " " + pos[1] + ")";
            }
          });

        layer.selectAll("g.textBoxWrapper text")
          .data(layerTextBoxes)
          .attr({
            "y": function(d) {
              $(this).find("tspan").attr("x", getTextBoxCoords(d)[0]);
              return getTextBoxCoords(d)[1];
            }
          });

        layer.selectAll("g.textBoxWrapper polygon")
          .data(layerTextBoxes.filter(function(d) {
            return d.calloutPoint;
          }))
          .attr({
            "callout-location-data": function(d) {
              var pos = getTextBoxCoords(d);
              return pos[4] + ", " + pos[5];
            }
          });
      };

      updateText(1);
      updateText(2);

      // update callouts
      $(".textBox").each(function() {
        var $parentNode = $(this.parentNode),
          callout = $parentNode.find("polygon"),
          frame = $parentNode.find("rect")[0],
          fontSize, width, height, calloutLocation;

        if (!frame || callout.length === 0) return;

        fontSize = parseFloat(this.getAttributeNS(null, "font-size"));
        width = frame.getAttribute("width");
        height = frame.getAttribute("height");
        calloutLocation = callout.attr("callout-location-data").split(", ");

        callout.attr("points", getCalloutPath(calloutLocation, frame, width, height, fontSize));
      });
    }

    function drawTextBoxes() {
      var size, layers, appendTextBoxes;
      // Workaround for a rendering bug in Chrome on OS X and Windows (but not Linux or Android);
      // see http://crbug.com/309740
      var shouldRoundTextBoxStrokeWidth =
        browser.browser === 'Chrome' && !!browser.oscpu.match(/Windows|Mac OS X/);

      textBoxes = model.get('textBoxes');

      size = [model.get('width'), model.get('height')];

      layers = [textContainerTop, textContainerBelow];

      // Append to either top or bottom layer depending on item's layer #.
      appendTextBoxes = function(layerNum) {
        var layer = layers[layerNum - 1],
          text, layerTextBoxes, selection;

        layer.selectAll("g.textBoxWrapper").remove();

        layerTextBoxes = textBoxes.filter(function(t) {
          return t.layer === layerNum;
        });

        selection = layer.selectAll("g.textBoxWrapper")
          .data(layerTextBoxes);
        text = selection.enter().append("svg:g")
          .attr("class", "textBoxWrapper");

        text.filter(function(d) {
          return d.calloutPoint;
        })
          .append("polygon")
          .attr({
            "points": "0,0 0,0 0,0",
            "style": function(d) {
              var backgroundColor = d.backgroundColor,
                strokeWidth = d.strokeWidthEms * fontSizeInPixels,
                strokeOpacity = d.strokeOpacity,
                strokeColor = d.strokeColor;
              return "fill:" + backgroundColor + ";opacity:1.0;fill-opacity:1;stroke:" + strokeColor + ";stroke-width:" + (strokeWidth * 2) + ";stroke-opacity:" + strokeOpacity;
            },
            "callout-location-data": function(d) {
              var pos = getTextBoxCoords(d);
              return pos[4] + ", " + pos[5];
            }
          });

        text.filter(function(d) {
          return d.frame;
        })
          .append("rect")
          .attr({
            "class": function(d, i) {
              return "textBoxFrame text-" + i;
            },
            "id": function(d, i) {
              return "text-" + i;
            },
            "transform": function(d) {
              var rotate = d.rotate,
                pos = getTextBoxCoords(d);
              return "rotate(" + rotate + " " + pos[0] + " " + pos[1] + ")";
            },
            "style": function(d) {
              var backgroundColor = d.backgroundColor,
                strokeWidth = d.strokeWidthEms * fontSizeInPixels,
                strokeOpacity = d.strokeOpacity,
                strokeColor = d.strokeColor;

              if (shouldRoundTextBoxStrokeWidth && strokeWidth < 1) {
                // Workaround for ghosting artifact left when stroke-width < 1 in Chrome on OS X.
                // Try to adjust opacity to compensate for increased width:
                strokeOpacity *= strokeWidth;
                strokeWidth = 1;
              }

              return "fill:" + backgroundColor + ";opacity:1.0;fill-opacity:1;stroke:" + strokeColor + ";stroke-width:" + strokeWidth + ";stroke-opacity:" + strokeOpacity;
            },
            "width": 0,
            "height": 0,
            "rx": function(d) {
              return d.frame === "rounded rectangle" ? model2px(d.fontSize) / 2.5 : 0;
            },
            "ry": function(d) {
              return d.frame === "rounded rectangle" ? model2px(d.fontSize) / 2 : 0;
            },
            "x": function(d) {
              return getTextBoxCoords(d)[2];
            },
            "y": function(d) {
              return getTextBoxCoords(d)[3];
            }
          });

        text.filter(function(d) {
          return d.calloutPoint;
        })
          .append("polygon")
          .attr({
            "points": "0,0 0,0 0,0",
            "style": function(d) {
              var backgroundColor = d.backgroundColor;
              return "fill:" + backgroundColor + ";opacity:1.0;fill-opacity:1;stroke:#000000;stroke-width:0;";
            }
          });

        text.append("text")
          .attr({
            "class": function() {
              return "textBox" + (AUTHORING ? " draggable" : "");
            },
            "transform": function(d) {
              var rotate = d.rotate,
                pos = getTextBoxCoords(d);
              return "rotate(" + rotate + " " + pos[0] + " " + pos[1] + ")";
            },
            "x-data": function(d) {
              return getTextBoxCoords(d)[0];
            },
            "y": function(d) {
              return getTextBoxCoords(d)[1];
            },
            "width-data": function(d) {
              return d.width;
            },
            "height-data": function(d) {
              return d.height;
            },
            "width": model2px(size[0]),
            "height": model2px(size[1]),
            "xml:space": "preserve",
            "font-family": "'" + labConfig.fontface + "', sans-serif",
            "font-size": function(d) {
              return model2px(d.fontSize) + "px";
            },
            "fill": function(d) {
              return d.color || "black";
            },
            "text-data": function(d) {
              return d.text;
            },
            "text-anchor": function(d) {
              var align = d.textAlign || "left";
              if (align === "center") align = "middle";
              return align;
            },
            "has-host": function(d) {
              return !!d.hostType;
            },
            "pointer-events": function(d) { return !d.hostType ? 'auto' : 'none'; },
          })
          .call(d3.behavior.drag()
            .on("drag", textDrag)
            .on("dragend", function(d) {
              // simple output to console for now, eventually should just get
              // serialized back to interactive (or model?) JSON on author save
              console.log('"x": ' + d.x + ",");
              console.log('"y": ' + d.y + ",");
            })
        );
        selection.exit().remove();
      };

      appendTextBoxes(1);
      appendTextBoxes(2);

      // wrap text, set callouts
      $(".textBox").each(function() {
        var text = this.getAttributeNS(null, "text-data"),
          x = this.getAttributeNS(null, "x-data"),
          width = this.getAttributeNS(null, "width-data"),
          height = this.getAttributeNS(null, "height-data"),
          fontSize = parseFloat(this.getAttributeNS(null, "font-size")),
          transform = this.getAttributeNS(null, "transform"),
          hasHost = this.getAttributeNS(null, "has-host"),
          textAlign = this.getAttributeNS(null, "text-anchor"),
          $parentNode = $(this.parentNode),
          horizontalPadding, verticalPadding,
          result, fullWidth, fullHeight, frame, dy, tx, ty,
          callout, calloutLocation;

        dy = fontSize * 1.2;
        horizontalPadding = +fontSize * 1.5;
        verticalPadding = fontSize / 1.8;

        if (width === '') {
          width = -1;
        } else {
          width = model2px(width);
        }

        if (height === '') {
          height = -1;
        } else {
          height = model2px(height);
        }

        while (this.firstChild) { // clear element first
          this.removeChild(this.firstChild);
        }

        result = wrapSVGText(text, this, width, x, dy);

        if ($parentNode.find("rect").length > 0) {
          frame = $parentNode.find("rect")[0];
          fullWidth = result.width + horizontalPadding;
          frame.setAttributeNS(null, "width", fullWidth);
          if (height > 0) {
            fullHeight = height;
          } else {
            fullHeight = (result.lines * dy) + verticalPadding;
          }
          frame.setAttributeNS(null, "height", fullHeight);
        }

        // if we have a callout
        callout = $parentNode.find("polygon");
        if (frame && callout.length > 0) {
          calloutLocation = callout.attr("callout-location-data").split(", ");
          callout.attr("points", getCalloutPath(calloutLocation, frame, fullWidth, fullHeight, fontSize));
        }

        // center all hosted labels simply by tweaking the g.transform
        if (textAlign === "middle") {
          tx = result.width / 2;
          if (height > 0) {
            ty = height / 2 - verticalPadding * 1.5 - (result.lines - 1) * dy / 2;
          } else {
            ty = 0;
          }
          transform = transform + " translate(" + tx + "," + ty + ")";
          $(this).attr("transform", transform);
        }
        if (hasHost === "true" && callout.length === 0) {
          tx = result.width / -2 - horizontalPadding / 2;
          ty = result.lines * dy / -2 - verticalPadding / 2;
          $parentNode.attr("transform", "translate(" + tx + "," + ty + ")");
        }
      });
    }

    function setupObstacles() {
      obstacles = model.get_obstacles();
      atomsContainer.selectAll("g.obstacle").remove();
      if (obstacles) {
        mockObstaclesArray.length = obstacles.x.length;
        obstacle = atomsContainer.selectAll("g.obstacle").data(mockObstaclesArray);
        obstacleEnter();
      }
    }

    function setupShapes() {
      shapes = model.get_shapes();
      shapeContainerTop.selectAll(".shape").remove();
      shapeContainerBelow.selectAll(".shape").remove();
      if (shapes) {
        mockShapesTop = [];
        mockShapesBelow = [];
        for (var i = 0; i < shapes.x.length; i++) {
          if (shapes.layer[i] === 1) {
            mockShapesTop.push({
              index: i,
              layerPosition: shapes.layerPosition[i]
            });
          } else {
            mockShapesBelow.push({
              index: i,
              layerPosition: shapes.layerPosition[i]
            });
          }
        }
        mockShapesTop.sort(function(a, b) {
          return a.layerPosition - b.layerPosition;
        });
        mockShapesBelow.sort(function(a, b) {
          return a.layerPosition - b.layerPosition;
        });
        shapeTop = shapeContainerTop.selectAll(".shape").data(mockShapesTop);
        shapeBelow = shapeContainerBelow.selectAll(".shape").data(mockShapesBelow);
        shapeEnter();
      }
    }

    function setupLines() {
      lines = model.get_lines();
      mockLinesTop.length = lines.x1.length;
      lineTop = lineContainerTop.selectAll(".line").data(mockLinesTop);
      lineTop.exit().remove();
      if (lines) {
        lineTop.enter().append("line");
        lineTop.attr({
          "class": "line",
          "x1": function(d, i) {
            return model2px(lines.x1[i]);
          },
          "y1": function(d, i) {
            return model2pxInv(lines.y1[i]);
          },
          "x2": function(d, i) {
            return model2px(lines.x2[i]);
          },
          "y2": function(d, i) {
            return model2pxInv(lines.y2[i]);
          },
          "stroke-width": function(d, i) {
            return lines.lineWeight[i];
          },
          "stroke-dasharray": function(d, i) {
            return lines.lineDashes[i];
          },
          "stroke": function(d, i) {
            return lines.lineColor[i];
          },
          "marker-start": function(d, i){
            return createCustomArrowHead(i, lines.beginStyle[i], true);
          },
          "marker-end": function(d, i){
            return createCustomArrowHead(i, lines.endStyle[i]);
          },
          "visibility": function(d, i) {
            return lines.visible[i] ? "visible" : "hidden";
          }
        });
      }
    }

    function setupAtomTrace() {
      atomsContainer.selectAll("path.atomTrace").remove();
      atomTracePath = "";

      drawAtomTrace = model.get("showAtomTrace");
      atomTraceId = model.get("atomTraceId");
      if (drawAtomTrace) {
        atomTrace = atomsContainer.selectAll("path.atomTrace").data([modelAtoms[atomTraceId]]);
        atomTraceEnter();
      }
    }

    function getAtomTracePath(d) {
      // until we implement buffered array model output properties,
      // we just keep the path history in the path string
      var dx = Math.floor(model2px(d.x) * 100) / 100,
        dy = Math.floor(model2pxInv(d.y) * 100) / 100,
        lIndex, sIndex;
      if (!atomTracePath) {
        atomTracePath = "M" + dx + "," + dy + "L";
        return "M " + dx + "," + dy;
      } else {
        atomTracePath += dx + "," + dy + " ";
      }

      // fake buffered array functionality by knocking out the first
      // element of the string when we get too big
      if (atomTracePath.length > 4000) {
        lIndex = atomTracePath.indexOf("L");
        sIndex = atomTracePath.indexOf(" ");
        atomTracePath = "M" + atomTracePath.slice(lIndex + 1, sIndex) + "L" + atomTracePath.slice(sIndex + 1);
      }
      return atomTracePath;
    }

    function updateAtomTrace() {
      atomTrace.attr({
        "d": getAtomTracePath
      });
    }

    function textDrag(d) {
      var dragDx = model2px.invert(d3.event.dx),
        dragDy = model2px.invert(d3.event.dy);

      if (!(AUTHORING && model.isStopped())) {
        // for now we don't have user-draggable textBoxes
        return;
      } else {
        d.x = d.x + dragDx;
        d.y = d.y - dragDy;
        updateTextBoxes();
      }
    }

    function setupBrowserWarning() {
      var b = benchmark.what_browser(); // we need to recalc this for FF, for some reason

      function setupWarningPane(testFunc, id, htmlContent) {
        if (!testFunc()) return;
        var $warning = modelView.$el.parent().find("#" + id);
        if ($warning.length === 0) {
          $warning = $("<div id='" + id + "' class='warning-pane'>" + htmlContent + "</div>");
          $warning.on("click", function () {
            $(this).fadeOut();
          });
          $warning.appendTo(modelView.$el.parent());
        }
        var pos = modelView.pos();
        $warning.css({
          width: pos.width - 20,
          top: pos.bottom - $warning.height() - 10,
          left: pos.left + 10
        });
      }

      // Firefox 18-23  warning:
      setupWarningPane(function () {
         return b.browser === "Firefox" &&
                b.version >= "18" && b.version < "23";
      }, "ff1823warning", "<a href='http://blog.concord.org/serious-performance-regression-in-firefox-18-and-newer' " +
                          "class='opens-in-new-window' target='_blank'>" +
                          "Firefox v18-22 performance issue...</a>)");

      // Firefox 27 + Windows warning:
      setupWarningPane(function () {
         return b.browser === "Firefox" &&
                b.oscpu.indexOf("Windows") !== -1 &&
                b.version >= "27" && b.version < "28";
      }, "ff27warning", "Because of a problem in " +
                        "Firefox 27 with certain types of graphics (SVG), you may see minor " +
                        "visual problems with some models. We recommend using another browser " +
                        "until Firefox 28 becomes available.");

      // Safari 6.1 + DNA models warning:
      setupWarningPane(function () {
         return model.get("DNA") &&
                b.browser === "Safari" &&
                b.version.indexOf("6.1") === 0;
      }, "saf61DNAwarning", "This model may have visual problems on Safari 6. We recommend " +
                            "upgrading Safari, or using another browser.");
    }

    function setupMiscOptions() {
      // These options are still used by the obstacle force arrows.
      forceVectorColor = model.get("forceVectors").color;
      forceVectorWidth = model.get("forceVectors").width;
      createVectorArrowHeads(forceVectorColor, "force");

      // Reset custom arrow heads.
      arrowHeadsCache = {};
      atomsContainer.selectAll(".custom-arrow-head").remove();

      atomTraceColor = model.get("atomTraceColor");
    }

    function setupRendererOptions() {
      useQuantumDynamics = model.properties.useQuantumDynamics;
      if (useQuantumDynamics) {
        createExcitationGlow();
      }

      createSymbolImages();

      // Initialize renderers.
      geneticRenderer = new GeneticRenderer(modelView, model);
    }

    function photonPath(d) {
      var lineData = [],
        nPoints = 40,
        line = d3.svg.line()
          .x(function(d) {
            return model2px(0.5 / nPoints * d.x);
          })
          .y(function(d) {
            return model2px(0.1 * d.y);
          }),

        t = d.angularFrequency * 2 * Math.PI / nPoints,
        i;

      // Reference implementation:
      // https://github.com/concord-consortium/mw/blob/6e2f2d4630323b8e993fcfb531a3e7cb06644fef/src/org/concord/mw2d/models/Photon.java#L74-L79
      for (i = 0; i < nPoints; i++) {
        lineData.push({
          x: i - nPoints / 2,
          y: Math.sin(i * t) / (1 + 0.01 * (i - 0.5 * nPoints) * (i - 0.5 * nPoints))
        });
      }

      return line(lineData);
    }

    function photonColor(data) {
      // Reference implementation:
      // https://github.com/concord-consortium/mw/blob/6e2f2d4630323b8e993fcfb531a3e7cb06644fef/src/org/concord/mw2d/models/Photon.java#L185-L205
      var freq = data.angularFrequency;
      if (freq <= MIN_VISIBLE_FREQ || freq >= MAX_VISIBLE_FREQ) {
        return "rgba(0,0,0,0.8)"
      } else {
        var d = (MAX_VISIBLE_FREQ - MIN_VISIBLE_FREQ) / 7.0;
        var i = Math.floor((freq - MIN_VISIBLE_FREQ) / d);
        if (i === 6) {
          return "rgb(" + PHOTON_COLORS[i].join(',') + ")";
        }
        else {
          var remainder = (freq - MIN_VISIBLE_FREQ - i * d) / d;
          var r1 = PHOTON_COLORS[i][0];     // red
          var r2 = PHOTON_COLORS[i + 1][0];
          var g1 = PHOTON_COLORS[i][1];     // green
          var g2 = PHOTON_COLORS[i + 1][1];
          var b1 = PHOTON_COLORS[i][2];     // blue
          var b2 = PHOTON_COLORS[i + 1][2];
          return "rgb(" + Math.floor(r1 + remainder * (r2 - r1)) + "," + Math.floor(g1 + remainder * (g2 - g1)) + ","
                        + Math.floor(b1 + remainder * (b2 - b1)) + ")";
        }
      }
    }

    function photonDashArray(data) {
      var freq = data.angularFrequency;
      if (freq <= MIN_VISIBLE_FREQ || freq >= MAX_VISIBLE_FREQ) {
        // SVG "stroke-dasharray" property value.
        return "4,2"
      } else {
        return null;
      }
    }

    function enterAndUpdatePhotons() {
      var photons = atomsContainer
        .selectAll(".photon")
        .data(model.getPhotons(), function(d) {
          return d.id;
        });

      photons.enter().append("path")
        .attr({
          "class": "photon",
          "d": photonPath,
          "stroke": photonColor,
          "stroke-dasharray": photonDashArray,
          "stroke-width": model2px(0.011),
          "fill-opacity": 0
        });

      photons.exit().remove();

      photons.attr("transform", function(d) {
        return "translate(" + model2px(d.x) + ", " + model2pxInv(d.y) + ") " +
          "rotate(" + d.angle + ")";
      });

    }

    //
    // *** Main Renderer functions ***
    //

    //
    // MD2D Renderer: setup
    //

    function setup() {
      model2px = modelView.model2px;
      model2pxInv = modelView.model2pxInv;

      fontSizeInPixels = modelView.getFontSizeInPixels();

      modelAtoms = model.getAtoms();
      modelElectricField = model.getElectricField();
      modelElements = model.get_elements();
      modelWidth = model.get('width');
      modelHeight = model.get('height');
      aspectRatio = modelWidth / modelHeight;

      setupRendererOptions();

      // Subscribe for model events.
      model.addPropertiesListener(["temperatureControl", "keShading"], drawSymbolImages);

      function redrawClickableObjects(redrawOperation) {
        return function() {
          redrawOperation();
          // All objects where repainted (probably removed and added again), so
          // it's necessary to apply click handlers again.
          modelView.updateClickHandlers();
        };
      }

      function setupElectricField() {
        electricFieldRenderer.setup();
        modelView.renderCanvas();
      }

      // Redraw container each time when some visual-related property is changed.
      model.addPropertiesListener([
          "chargeShading", "chargeShadingStyle", "showChargeSymbols", "useThreeLetterCode",
          "showAtomTrace", "atomTraceId", "aminoAcidColorScheme",
          "backgroundColor", "markColor", "forceVectorsDirectionOnly"
        ],
        redrawClickableObjects(repaint));

      // Vectors:
      model.addPropertiesListener(["electricFieldDensity", "showElectricField", "electricFieldColor"],
        setupElectricField);
      model.addPropertiesListener(["showVelocityVectors", "velocityVectors"], function () {
        velocityVectorsRenderer.setup();
        modelView.renderCanvas();
      });
      model.addPropertiesListener(["showForceVectors", "forceVectors", "forceVectorsDirectionOnly"], function () {
        forceVectorsRenderer.setup();
        modelView.renderCanvas();
      });
      model.addPropertiesListener(["showVDWLines", "VDWLinesCutoff"], function () {
        vdwLinesRenderer.setup();
        modelView.renderCanvas();
      });

      model.on('addAtom', redrawClickableObjects(function () {
        atomsRenderer.setup();
        velocityVectorsRenderer.setup();
        forceVectorsRenderer.setup();
        electricFieldRenderer.update();
        modelView.renderCanvas();
      }));
      model.on('removeAtom', redrawClickableObjects(function () {
        atomsRenderer.setup();
        velocityVectorsRenderer.setup();
        forceVectorsRenderer.setup();
        electricFieldRenderer.update();
        modelView.renderCanvas();
      }));
      model.on('addRadialBond', redrawClickableObjects(function () {
        bondsRenderer.setup();
        modelView.renderCanvas();
      }));
      model.on('removeRadialBond', redrawClickableObjects(function () {
        bondsRenderer.setup();
        modelView.renderCanvas();
      }));
      model.on('textBoxesChanged', redrawClickableObjects(drawTextBoxes));
      model.on('imagesChanged', redrawClickableObjects(imagesRenderer.setup));
      model.on('addElectricField', setupElectricField);
      model.on('removeElectricField', setupElectricField);
      model.on('changeElectricField', setupElectricField);

      setupBrowserWarning();

      isSetup = true;
    }

    // Call when model is reset or reloaded.

    function bindModel(newModel) {
      model = newModel;
      atomsRenderer.bindModel(newModel);
      bondsRenderer.bindModel(newModel);
      vdwLinesRenderer.bindModel(newModel);
      imagesRenderer.bindModel(newModel);
      setup();
    }

    //
    // MD2D Renderer: repaint
    //
    // Call when container being rendered into changes size, in that case
    // pass in new D3 scales for model2px transformations.
    //
    // Also call when the number of objects changes such that the container
    // must be setup again.
    //

    function repaint(m2px, m2pxInv) {
      if (arguments.length) {
        model2px = m2px;
        model2pxInv = m2pxInv;
      }
      fontSizeInPixels = modelView.getFontSizeInPixels();

      setupMiscOptions();
      setupObstacles();
      atomsRenderer.setup();
      bondsRenderer.setup();
      vdwLinesRenderer.setup();
      setupShapes();
      setupLines();
      geneticRenderer.setup();
      velocityVectorsRenderer.setup();
      forceVectorsRenderer.setup();
      electricFieldRenderer.setup();
      setupAtomTrace();
      imagesRenderer.setup();
      drawTextBoxes();
      drawSymbolImages();
      setupBrowserWarning();
      if (useQuantumDynamics) {
        enterAndUpdatePhotons();
      }
    }

    //
    // MD2D Renderer: update
    //
    // Call to update visualization when model result state changes.
    // Normally called on every model tick.
    //

    function update() {
      console.time('view update');
      if (obstacles) {
        obstacle.attr("transform", function(d, i) {
          return "translate(" + model2px(obstacles.x[i]) + " " + model2pxInv(obstacles.y[i] + obstacles.height[i]) + ")";
        });
      }

      if (shapes) {
        shapeTop.attr("transform", function(d) {
          return "translate(" + model2px(shapes.x[d.index]) + " " + model2pxInv(shapes.y[d.index] + shapes.height[d.index]) + ")";
        });
        shapeBelow.attr("transform", function(d) {
          return "translate(" + model2px(shapes.x[d.index]) + " " + model2pxInv(shapes.y[d.index] + shapes.height[d.index]) + ")";
        });
      }

      atomsRenderer.update();
      bondsRenderer.update();
      vdwLinesRenderer.update();
      velocityVectorsRenderer.update();
      forceVectorsRenderer.update();
      electricFieldRenderer.update();

      if (drawAtomTrace) {
        updateAtomTrace();
      }
      if (model.properties.images && model.properties.images.length !== 0) {
        imagesRenderer.update();
      }
      if (textBoxes && textBoxes.length > 0 && !onlyStaticTextBoxes) {
        // Update text boxes properties only when at least one of them is attached to some movable
        // object like atom or obstacle.
        updateTextBoxes();
      }
      if (useQuantumDynamics) {
        enterAndUpdatePhotons();
      }
      console.timeEnd('view update');
    }

    //
    // Public API to instantiated Renderer
    //
    api = {
      // Expose private methods.
      setup: function() {
        if (!isSetup) {
          setup();
        }
      },

      update: function() {
        if (isSetup) {
          update();
        }
      },

      repaint: function() {
        if (isSetup) {
          repaint();
        }
      },

      bindModel: bindModel
    };

    return api;
  };
});

/*global define: false */

define('models/md2d/views/view',['require','common/views/svg-container','models/md2d/views/renderer'],function (require) {
  var SVGContainer = require('common/views/svg-container'),
      Renderer     = require('models/md2d/views/renderer');

  return function (model, modelUrl, i18n) {
    return new SVGContainer(model, modelUrl, Renderer, {i18n: i18n});
  };

});

/*global $, define */

define('models/md2d/views/dna-edit-dialog',[],function () {

  return function DNAEditDialog(model) {
    var api,
        $dialogDiv,
        $dnaTextInput,
        $originalDnaInput,
        $errorMsg,
        $submitButton,
        originalDNA = model.get("DNA"),

        init = function() {
          // Basic dialog elements.
          $dialogDiv = $('<div></div>');
          $originalDnaInput = $('<input type="text" size="55" disabled/>');
          $originalDnaInput.val(originalDNA);
          $('<div>Original sequence</div>').append($originalDnaInput).appendTo($dialogDiv);
          $dnaTextInput = $('<input type="text" id="dna-sequence-input" size="55"/>');
          $('<div>Edited sequence</div>').prepend($dnaTextInput).appendTo($dialogDiv);
          $errorMsg = $('<p class="error"></p>');
          $errorMsg.appendTo($dialogDiv);

          // jQuery UI Dialog.
          $dialogDiv.dialog({
            dialogClass: "dna-edit-dialog",
            // Ensure that font is being scaled dynamically.
            appendTo: ".lab-responsive-content",
            title: "DNA Code on Sense Strand",
            autoOpen: false,
            width: "35em",
            buttons: {
              "Apply": function () {
                model.set({
                  DNA: $dnaTextInput.val()
                });
                $(this).dialog("close");
              }
            }
          });

          // Dynamic validation on input.
          $submitButton = $(".dna-edit-dialog button:contains('Apply')");
          $dnaTextInput.on("input", function () {
            try {
              model.getPropertyValidateFunc("DNA")($dnaTextInput.val());
              $submitButton.button("enable");
              $errorMsg.text("");
            } catch (e) {
              $submitButton.button("disable");
              $errorMsg.text(e.message);
            }
          });
        };

    api = {
      open: function () {
        // Clear previous errors.
        $errorMsg.text("");
        $submitButton.removeAttr("disabled");
        // Set current value of DNA code.
        $dnaTextInput.val(model.get("DNA"));
        $dialogDiv.dialog("open");
      },
      bindModel: function (newModel) {
        if (newModel !== model) {
          model = newModel;
          // Model has changed, so update original DNA too.
          originalDNA = model.get("DNA");
          $originalDnaInput.val(originalDNA);
        }
      }
    };

    init();

    return api;
  };
});

/*global define */

define('models/md2d/controllers/scripting-api',['require','models/md2d/views/dna-edit-dialog'],function (require) {

  var DNAEditDialog = require('models/md2d/views/dna-edit-dialog');

  /**
    Define the model-specific MD2D scripting API used by 'action' scripts on interactive elements.

    The universal Interactive scripting API is extended with the properties of the
    object below which will be exposed to the interactive's 'action' scripts as if
    they were local vars. All other names (including all globals, but excluding
    Javascript builtins) will be unavailable in the script context; and scripts
    are run in strict mode so they don't accidentally expose or read globals.

    @param: parent Scripting API
  */
  return function MD2DScriptingAPI (parent) {

    var dnaEditDialog = null,
        // whether we are currently processing a batch command, suppresses repaint
        batchDepth = 0;

    var setProperty = function () {
      var setter = arguments[0],
          i      = arguments[1],
          args;

      if (Array.isArray(i)) {
        args = Array.prototype.slice.call(arguments, 2, arguments.length);
        parent.api.batch( function() {
          for (var j = 0, jj = i[0]; j < i.length; jj = i[++j]) {
            setter.apply(null,Array.prototype.concat(jj, args));
          }
        });
      } else {
        args = Array.prototype.slice.call(arguments, 1, arguments.length);
        setter.apply(null,args);
      }
      parent.api.repaintIfReady();
    };

    function createModelAdder(methodName) {
      return function modelAdder(props, options) {
        try {
          parent.model[methodName].call(parent.model, props);
        } catch (e) {
          if (!options || !options.silent)
            throw e;
        }
        parent.api.repaintIfReady();
      };
    }

    return {

      getCurrentComputerTime: function() {
        return Date.now();
      },

      /* Returns number of atoms in the system. */
      getNumberOfAtoms: function getNumberOfAtoms(f) {
        return parent.model.getNumberOfAtoms(f);
      },

      /* Returns number of obstacles in the system. */
      getNumberOfObstacles: function getNumberOfObstacles() {
        return parent.model.getNumberOfObstacles();
      },

      /* Returns number of elements in the system. */
      getNumberOfElements: function getNumberOfElements() {
        return parent.model.getNumberOfElements();
      },

      /* Returns number of radial bonds in the system. */
      getNumberOfRadialBonds: function getNumberOfRadialBonds() {
        return parent.model.getNumberOfRadialBonds();
      },

      /* Returns number of angular bonds in the system. */
      getNumberOfAngularBonds: function getNumberOfAngularBonds() {
        return parent.model.getNumberOfAngularBonds();
      },

      addAtom: function addAtom(props) {
        return parent.model.addAtom(props);
      },

      /*
        Removes atom 'i'.
      */
      removeAtom: function removeAtom(i, options) {
        try {
          parent.model.removeAtom(i, options);
        } catch (e) {
          if (!options || !options.silent)
            throw e;
        }
      },

      /*
        Removes radial bond 'i'.
      */
      removeRadialBond: function removeRadialBond(i, options) {
        try {
          parent.model.removeRadialBond(i);
        } catch (e) {
          if (!options || !options.silent)
            throw e;
        }

        parent.api.repaintIfReady();
      },

      /*
        Removes angular bond 'i'.
      */
      removeAngularBond: function removeAngularBond(i, options) {
        try {
          parent.model.removeAngularBond(i);
        } catch (e) {
          if (!options || !options.silent)
            throw e;
        }

        parent.api.repaintIfReady();
      },

      /*
        Adds radial bond with properties 'props'.
      */
      addRadialBond: function addRadialBond(props) {
        parent.model.addRadialBond(props);

        parent.api.repaintIfReady();
      },

      /*
        Adds angular bond with properties 'props'.
      */
      addAngularBond: function addAngularBond(props) {
        parent.model.addAngularBond(props);

        parent.api.repaintIfReady();
      },

      addRandomAtom: function addRandomAtom() {
        return parent.model.addRandomAtom.apply(parent.model, arguments);
      },

      adjustTemperature: function adjustTemperature(fraction) {
        parent.model.set({targetTemperature: fraction * parent.model.get('targetTemperature')});
      },

      /**
       * Scales the velocity of all atoms to the desired temperature T.
       * @param {number} T           defined in K
       */
      setTemperatureOfAllAtoms: function setTemperatureOfAllAtoms(T) {
        var atomIndices = [];
        for (var i = 0; i < parent.model.getNumberOfAtoms(); i++) {
          atomIndices.push(i);
        }
        parent.model.setTemperatureOfAtoms(atomIndices, T);
      },

      /**
       * Scales the velocity of a group of atoms to the desired temperature T.
       * @param {array}  atomIndices
       * @param {number} T           defined in K
       */
      setTemperatureOfAtoms: function setTemperatureOfAtoms(atomIndices, T) {
        parent.model.setTemperatureOfAtoms(atomIndices, T);
      },

      /**
       * Adds energy defined in eV to a group of atoms.
       * @param {number} energy      defined in eV
       * @param {array}  atomIndices optional, if undefined, KE will be added to all atoms
       */
      addKEToAtoms: function addKEToAtoms(energy, atomIndices) {
        parent.model.addKEToAtoms(energy, atomIndices);
      },

      getTemperatureOfAllAtoms: function getTemperatureOfAllAtoms() {
        var atomIndices = [];
        for (var i = 0; i < parent.model.getNumberOfAtoms(); i++) {
          atomIndices.push(i);
        }
        return parent.model.getTemperatureOfAtoms(atomIndices);
      },

      getTemperatureOfAtoms: function getTemperatureOfAtoms(atomIndices) {
        return parent.model.getTemperatureOfAtoms(atomIndices);
      },

      limitHighTemperature: function limitHighTemperature(t) {
        if (parent.model.get('targetTemperature') > t) parent.model.set({targetTemperature: t});
      },

      /** returns a list of integers corresponding to atoms in the system */
      randomAtoms: function randomAtoms(n) {
        var numAtoms = parent.model.getNumberOfAtoms();

        if (n === null) n = 1 + parent.api.randomInteger(numAtoms-1);

        if (!parent.api.isInteger(n)) throw new Error("randomAtoms: number of atoms requested, " + n + ", is not an integer.");
        if (n < 0) throw new Error("randomAtoms: number of atoms requested, " + n + ", was less be greater than zero.");

        if (n > numAtoms) n = numAtoms;
        return parent.api.choose(n, numAtoms);
      },

      /**
        Quantum Dynamics
      */

      /** Turn on quantum dynamics light source. */
      turnOnLightSource: function turnOnLightSource() {
        parent.model.turnOnLightSource();
      },

      turnOffLightSource: function turnOffLightSource() {
        parent.model.turnOffLightSource();
      },

      setLightSourceAngle: function setLightSourceAngle(angle) {
        parent.model.setLightSourceAngle(angle);
      },

      setLightSourceFrequency: function setLightSourceFrequency(freq) {
        parent.model.setLightSourceFrequency(freq);
      },

      setLightSourcePeriod: function setLightSourcePeriod(period) {
        parent.model.setLightSourcePeriod(period);
      },

      setLightSourceNumber: function setLightSourceNumber(number) {
        parent.model.setLightSourceNumber(number);
      },

      /**
       * Chemical Reactions
       */

      /**
       * Sets bond energy (dissociation energy) of a bond.
       * @param {string} bondDescription e.g. "1-1" means single bond between element 1 and 1,
       *                                 "1=2" means double bond between element 1 and 2 etc.
       * @param {number} value           bond energy in eV
       */
      setBondEnergy: function setBondEnergy(bondDescription, value) {
        parent.model.setBondEnergy(bondDescription, value);
      },

      /**
       * Sets valence electrons count of the given element.
       * @param {number} element
       * @param {number} value
       */
      setValenceElectrons: function setVelenceElectrons(element, value) {
        parent.model.setValenceElectrons(element, value);
        parent.api.repaintIfReady();
      },

      /**
       * Returns array of atom indices within circular area,
       * optionally specifying an element of interest.
       * e.g. atomsWithinCircle(1, 1, 0.5) returns all atoms within 0.5 nm of position (1nm, 1nm).
       * @param  {number} x       X coordinate of the circle center.
       * @param  {number} y       Y coordinate of the circle center.
       * @param  {number} w       Radius of the circle.
       * @param  {number} element Optional ID of the desired element type.
       * @return {Array}          Array of atoms indices within a given area.
       */
      atomsWithinCircle: function(x, y, r, element) {
        var result = [],
            props, dist, i, len;

        for (i = 0, len = parent.model.getNumberOfAtoms(); i < len; i++) {
          props = parent.model.getAtomProperties(i);
          if (typeof element !== 'undefined' && props.element !== element) continue;
          dist = Math.sqrt(Math.pow(x - props.x, 2) + Math.pow(y - props.y, 2));
          if (dist <= r) {
            result.push(i);
          }
        }
        return result;
      },

      /**
       * Returns array of atom indices within rectangular area,
       * optionally specifying an element of interest.
       * e.g. atomsWithinRect(1, 1, 0.2, 0.3) returns all atoms within a rectangle of width 0.2nm
       * by height 0.3nm, with the bottom-left corner specified by the postion (1nm, 1nm).
       * @param  {number} x       X coordinate of the bottom-left rectangle corner.
       * @param  {number} y       Y coordinate of the bottom-left rectangle corner.
       * @param  {number} w       Width of the rectangle.
       * @param  {number} h       Height of the rectangle.
       * @param  {number} element Optional ID of the desired element type.
       * @return {Array}          Array of atoms indices within a given area.
       */
      atomsWithinRect: function(x, y, w, h, element) {
        var result = [],
            props, dist, inX, inY, i, len;

        for (i = 0, len = parent.model.getNumberOfAtoms(); i < len; i++) {
          props = parent.model.getAtomProperties(i);
          if (typeof element !== 'undefined' && props.element !== element) continue;
          if (typeof h === 'undefined') {
            dist = Math.sqrt(Math.pow(x - props.x, 2) + Math.pow(y - props.y, 2));
            if (dist <= w) {
              result.push(i);
            }
          } else {
            inX = ((props.x >= x) && (props.x <= (x + w)));
            inY = ((props.y >= y) && (props.y <= (y + h)));
            if (inX && inY) {
              result.push(i);
            }
          }
        }
        return result;
      },

      /**
       * Returns an array of atom indices within triangular area,
       * optionally specifying an element ID of interest.
       *
       * @param  {number} ax      X coordinate of 1st triangle vertex.
       * @param  {number} ay      Y coordinate of 1st triangle vertex.
       * @param  {number} bx      X coordinate of 2nd triangle vertex.
       * @param  {number} by      Y coordinate of 2nd triangle vertex.
       * @param  {number} cx      X coordinate of 3rd triangle vertex.
       * @param  {number} cy      Y coordinate of 3rd triangle vertex.
       * @param  {number} element Optional ID of the desired element type.
       * @return {Array}          Array of atoms indices within a given area.
       */
      atomsWithinTriangle: function(ax, ay, bx, by, cx, cy, element) {
        var result = [],
            props, i, len;

        function isInTriangle(px, py) {
          // See: http://www.blackpawn.com/texts/pointinpoly/default.html
          var v0 = [cx - ax, cy - ay],
              v1 = [bx - ax, by - ay],
              v2 = [px - ax, py - ay],

              dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1]),
              dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1]),
              dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1]),
              dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1]),
              dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1]),

              invDenom = 1 / (dot00 * dot11 - dot01 * dot01),

              u = (dot11 * dot02 - dot01 * dot12) * invDenom,
              v = (dot00 * dot12 - dot01 * dot02) * invDenom;

          return ((u >= 0) && (v >= 0) && (u + v < 1));
        }

        for (i = 0, len = parent.model.getNumberOfAtoms(); i < len; i++) {
          props = parent.model.getAtomProperties(i);
          if (typeof element !== 'undefined' && props.element !== element) continue;
          if (isInTriangle(props.x, props.y)) {
            result.push(i);
          }
        }
        return result;
      },

      /**
        Accepts atom indices as arguments, or an array containing atom indices.
        Unmarks all atoms, then marks the requested atom indices.
        Repaints the screen to make the marks visible.
      */
      markAtoms: function markAtoms(indices) {
        var i,
            len;

        if (arguments.length === 0) return;

        // allow passing a list of arguments instead of an array of atom indices
        if (!parent.api.isArray(arguments[0])) {
          indices = arguments;
        }

        parent.api.unmarkAllAtoms();

        // mark the requested atoms
        for (i = 0, len = indices.length; i < len; i++) {
          parent.model.setAtomProperties(indices[i], {marked: 1});
        }
        parent.api.repaintIfReady();
      },

      unmarkAllAtoms: function unmarkAllAtoms() {
        for (var i = 0, len = parent.model.getNumberOfAtoms(); i < len; i++) {
          parent.model.setAtomProperties(i, {marked: 0});
        }
        parent.api.repaintIfReady();
      },

      traceAtom: function traceAtom(i) {
        if (i === null) return;

        parent.model.set({atomTraceId: i});
        parent.model.set({showAtomTrace: true});
      },

      untraceAtom: function untraceAtom() {
        parent.model.set({showAtomTrace: false});
      },

      /**
        Sets individual atom properties using human-readable hash.
        e.g. setAtomProperties(5, {x: 1, y: 0.5, charge: 1})
      */
      setAtomProperties: function setAtomProperties(i, props, checkLocation, moveMolecule) {
        setProperty(parent.model.setAtomProperties, i, props, checkLocation, moveMolecule);
      },

      /**
       * Returns atom transition object. It can be used to smoothly change
       * atom properties over specified time. It's similar to D3 transitions.
       *
       * Atom transition object provides following methods:
       *  id(id)          - sets ID of the atom (required!).
       *  duration(d)     - sets duration in ms (required!).
       *  prop(name, val) - sets property name and its final value (required!).
       *  delay(d)        - sets delay in ms (default is 0).
       *  ease(name)      - sets easing function (default is "cubic-in-out").
       *                    Please see:
       *                    https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease
       *
       * e.g.
       *  atomTransition().id(0).duration(1000).ease("linear").prop("x", 10);
       *
       * This will change "x" property of the atom with ID=0
       * to value 10 over 1000ms using linear easing function.
       *
       * @return {AtomTransition} AtomTransition instance.
       */
      atomTransition: function atomTransition() {
        return parent.model.atomTransition();
      },

      /**
        Returns atom properties as a human-readable hash.
        e.g. getAtomProperties(5) --> {x: 1, y: 0.5, charge: 1, ... }
      */
      getAtomProperties: function getAtomProperties(i) {
        return parent.model.getAtomProperties(i);
      },

      /**
        Returns an array consisting of radial bonds indices for the atom
        e.g. getRadialBondsForAtom(5) --> [2]
      */
      getRadialBondsForAtom: function getRadialBondsForAtom(i) {
        return parent.model.getRadialBondsForAtom(i);
      },

      /**
        Returns an array consisting of the angular bonds indices for the atom
        e.g. getAngularBondsForAtom(5) --> [6, 8]
      */
      getAngularBondsForAtom: function getAngularBondsForAtom(i) {
        return parent.model.getAngularBondsForAtom(i);
      },

      /**
        Returns all atoms in the same molecule as atom i
        (not including i itself)
      */
      getMoleculeAtoms: function getMoleculeAtoms(i) {
        return parent.model.getMoleculeAtoms(i);
      },

      setElementProperties: function setElementProperties(i, props) {
        setProperty(parent.model.setElementProperties, i, props);
      },

      /**
        Sets custom pairwise LJ properties (epsilon or sigma), which will
        be used instead of the mean values of appropriate element properties.
        i, j - IDs of the elements which should have custom pairwise LJ properties.
        props - object containing sigma, epsilon or both.
        e.g. setPairwiseLJProperties(0, 1, {epsilon: -0.2})
      */
      setPairwiseLJProperties: function setPairwiseLJProperties(i, j, props) {
        parent.model.getPairwiseLJProperties().set(i, j, props);
      },

      /**
        Removes custom pairwise LJ properties, reverting it to the default calculation.
      */
      removePairwiseLJProperties: function removePairwiseLJProperties(i, j) {
        parent.model.getPairwiseLJProperties().remove(i, j);
      },

      getElementProperties: function getElementProperties(i) {
        return parent.model.getElementProperties(i);
      },

      /**
        Adds an obstacle/shape/line using human-readable hash of properties.
        e.g. addObstacle({x: 1, y: 0.5, width: 1, height: 1})
      */
      addObstacle: createModelAdder("addObstacle"),
      addShape: createModelAdder("addShape"),
      addLine: createModelAdder("addLine"),

      /**
        Sets individual obstacle properties using human-readable hash.
        e.g. setObstacleProperties(0, {x: 1, y: 0.5, externalAx: 0.00001})
      */
      setObstacleProperties: function setObstacleProperties(i, props) {
        setProperty(parent.model.setObstacleProperties, i, props);
      },

      /**
        Returns obstacle properties as a human-readable hash.
        e.g. getObstacleProperties(0) --> {x: 1, y: 0.5, externalAx: 0.00001, ... }
      */
      getObstacleProperties: function getObstacleProperties(i) {
        return parent.model.getObstacleProperties(i);
      },

      /**
        Removes obstacle 'i'.
      */
      removeObstacle: function removeObstacle(i, options) {
        try {
          parent.model.removeObstacle(i);
        } catch (e) {
          if (!options || !options.silent)
            throw e;
        }

        parent.api.repaintIfReady();
      },

      setShapeProperties: function setShapeProperties(i, props) {
        setProperty( parent.model.setShapeProperties, i, props );
      },

      getShapeProperties: function getShapeProperties(i) {
        return parent.model.getShapeProperties(i);
      },

      setLineProperties: function setLineProperties(i, props) {
        setProperty(parent.model.setLineProperties, i, props);
      },

      getLineProperties: function getLineProperties(i) {
        return parent.model.getLineProperties(i);
      },

      addElectricField: function addElectricField(props, options) {
        try {
          parent.model.addElectricField(props);
        } catch (e) {
          if (!options || !options.silent)
            throw e;
        }
      },

      removeElectricField: function removeElectricField(i, options) {
        try {
          parent.model.removeElectricField(i);
        } catch (e) {
          if (!options || !options.silent)
            throw e;
        }
      },

      setElectricFieldProperties: function setElectricFieldProperties(i, props) {
        parent.model.setElectricFieldProperties(i, props);
      },

      getElectricFieldProperties: function getElectricFieldProperties(i) {
        return parent.model.getElectricFieldProperties(i);
      },

      getAtomsWithinShape: function getAtomsInsideShape(i) {
        var props=parent.model.getShapeProperties(i);
        return this.atomsWithinRect(props.x, props.y, props.width, props.height);
      },

      removeShape: function removeShape(i, options) {
        try {
          parent.model.removeShape(i);
        } catch (e) {
          if (!options || !options.silent)
            throw e;
        }
        parent.api.repaintIfReady();
      },

      removeLine: function removeLine(i, options) {
        try {
          parent.model.removeLine(i);
        } catch (e) {
          if (!options || !options.silent)
            throw e;
        }
        parent.api.repaintIfReady();
      },

      setRadialBondProperties: function setRadialBondProperties(i, props) {
        parent.model.setRadialBondProperties(i, props);
        parent.api.repaintIfReady();
      },

      getRadialBondProperties: function getRadialBondProperties(i) {
        return parent.model.getRadialBondProperties(i);
      },

      setAngularBondProperties: function setAngularBondProperties(i, props) {
        parent.model.setAngularBondProperties(i, props);
        parent.api.repaintIfReady();
      },

      getAngularBondProperties: function getAngularBondProperties(i) {
        return parent.model.getAngularBondProperties(i);
      },

      /**
        Opens DNA properties dialog, which allows to set DNA code.
      */
      openDNADialog: function openDNADialog() {
        if (dnaEditDialog == null) {
          dnaEditDialog = new DNAEditDialog(parent.model);
        }
        dnaEditDialog.bindModel(parent.model);
        dnaEditDialog.open();
      },

      /**
       * Jumps to the next DNA state.
       *
       * Note that jumping between translation states is not supported!
       * Please use animateToNextDNAState if you need to change state
       * from translation:x to translation:x+1.
       */
      jumpToNextDNAState: function jumpToNextDNAState() {
        parent.model.geneticEngine().jumpToNextState();
      },

      /**
       * Jumps to the next DNA state.
       *
       * Note that jumping between translation states is not supported!
       * When current state is translation:x, where x > 0, this functions
       * will cause jump to translation:0 state.
       */
      jumpToPrevDNAState: function jumpToPrevDNAState() {
        parent.model.geneticEngine().jumpToPrevState();
      },

      /**
       * Tests whether *current* DNA state is before state
       * passed as an argument.
       * @param {String} state DNA state name, e.g. "translation:5".
       * @return {boolean}     true if current state is before 'state',
       *                       false otherwise.
       */
      DNAStateBefore: function DNAStateBefore(state) {
        return parent.model.geneticEngine().stateBefore(state);
      },

      /**
       * Tests whether *current* DNA state is after state
       * passed as an argument.
       * @param {String} state DNA state name, e.g. "translation:5".
       * @return {boolean}     true if current state is after 'state',
       *                       false otherwise.
       */
      DNAStateAfter: function DNAStateAfter(state) {
        return parent.model.geneticEngine().stateAfter(state);
      },

      /**
       * Triggers animation to the next DNA state.
       *
       * Note that this is the only possible way to change state
       * from translation:x to translation:x+1. Jumping between
       * translation states is not supported!
       */
      animateToNextDNAState: function animateToNextDNAState() {
        parent.model.geneticEngine().transitionToNextState();
      },

      /**
       * Triggers animation to the given DNA state.
       * If current DNA state is after the desired state,
       * nothing happens.
       * e.g.
       * get('DNAState'); // transcription:0
       * animateToDNAState("transcription-end") // triggers animation
       * However:
       * get('DNAState'); // translation-end
       * animateToDNAState("transcription-end") // nothing happens
       *
       * @param  {string} stateName name of the state.
       */
      animateToDNAState: function animateToDNAState(stateName) {
        parent.model.geneticEngine().transitionTo(stateName);
      },

      /**
       * Stops current DNA animation.
       */
      stopDNAAnimation: function stopDNAAnimation() {
        // Jumping to previous state will cancel current animation
        // and cleanup transitions queue.
        parent.model.geneticEngine().stopTransition();
      },

      /**
       * Triggers only one step of DNA transcription.
       * This method also accepts optional parameter - expected nucleotide.
       * When it's available, transcription step will be performed only
       * when passed nucleotide code matches nucleotide, which should
       * be actually joined to mRNA in this transcription step. When
       * expected nucleotide code is wrong, this method does nothing.
       *
       * e.g.
       * transcribeDNAStep("A") will perform transcription step only
       * if "A" nucleotide should be added to mRNA in this step.
       *
       * @param {string} expectedNucleotide code of the expected nucleotide ("U", "C", "A" or "G").
       */
      transcribeDNAStep: function transcribeDNAStep(expectedNucleotide) {
        var ge = parent.model.geneticEngine();
        if (ge.stateBefore("dna") || ge.stateAfter("transcription-end")) {
          // Jump to beginning of DNA transcription if current state is before
          // or after transcrption process (so, state is different from:
          // "dna", "transcription:0", ..., "transcription-end").
          parent.model.set("DNAState", "dna");
          ge.transitionTo("transcription:0");
          ge.transcribeStep(expectedNucleotide);
        } else if (parent.model.get("DNAState") !== "transcription-end") {
          // Proceed to the next step.
          ge.transcribeStep(expectedNucleotide);
        }
      },

      /**
       * Triggers only one step of DNA translation.
       */
      translateDNAStep: function translateDNAStep() {
        var ge = parent.model.geneticEngine();
        if (ge.stateBefore("translation:0") || ge.stateAfter("translation-end")) {
          // Animate directly to the translation:0, merge a few shorter
          // animations.
          parent.model.set("DNAState", "translation:0");
          ge.transitionTo("translation:1");
        } else if (parent.model.get("DNAState") !== "translation-end") {
          // Proceed to the next step.
          ge.transitionToNextState();
        }
      },

     /**
      * Generates a random protein. It removes all existing atoms before.
      *
      * @param  {[type]} expectedLength controls the maximum (and expected) number of amino acids of
      *                                 the resulting protein. When expected length is too big
      *                                 (due to limited area of the model), protein will be truncated
      *                                 and warning shown.
      */
      generateRandomProtein: function (expectedLength) {
        var realLength = parent.model.geneticEngine().generateProtein(undefined, expectedLength);

        if (realLength !== expectedLength) {
          throw new Error("Generated protein was truncated due to limited area of the parent.model. Only" +
            realLength + " amino acids were generated.");
        }
      },

      /**
        Sets solvent. You can use three predefined solvents: "water", "oil" or "vacuum".
        This is only a convenience method. The same effect can be achieved by manual setting
        of 'solventForceFactor', 'dielectricConstant' and 'backgroundColor' properties.
      */
      setSolvent: function setSolvent(type) {
        parent.model.setSolvent(type);
      },

      pe: function pe() {
        return parent.model.get('potentialEnergy');
      },

      ke: function ke() {
        return parent.model.get('kineticEnergy');
      },

      atomsKe: function atomsKe(atomsList) {
        var sum = 0, i;
        for (i = 0; i < atomsList.length; i++) {
          sum += parent.model.getAtomKineticEnergy(atomsList[i]);
        }
        return sum;
      },

      minimizeEnergy: function minimizeEnergy() {
        parent.model.minimizeEnergy();
        parent.api.repaintIfReady();
      },

      addTextBox: function(props) {
        parent.model.addTextBox(props);
      },

      removeTextBox: function(i) {
        parent.model.removeTextBox(i);
      },

      setTextBoxProperties: function(i, props) {
        parent.model.setTextBoxProperties(i, props);
      },

      getTextBoxProperties: function(i) {
        return parent.model.getTextBoxProperties(i);
      },

      getNumberOfTextBoxes: function() {
        return parent.model.getNumberOfTextBoxes();
      },

      getNumberOfLines: function() {
        return parent.model.getNumberOfLines();
      },

      getNumberOfShapes: function() {
        return parent.model.getNumberOfShapes();
      },

      getImageProperties: function(i) {
        return parent.model.getImageProperties(i);
      },

      setImageProperties: function(i, props) {
        setProperty(parent.model.setImageProperties, i, props);
      },

      repaintIfReady: function() {
        if (batchDepth === 0) {
          parent.api.repaint();
        }
      },

      batch: function(func) {
        ++batchDepth;

        parent.model.startBatch();
        func();
        parent.model.endBatch();

        --batchDepth;

        // call repaint manually
        parent.api.repaintIfReady();
      }

    };

  };
});

/*global define, d3 */

define('models/md2d/benchmarks/benchmarks',['require','common/performance'],function (require) {

  var performance = require("common/performance"),

      TEST_TIME = 5000,
      WARMUP_TIME = 1000;

  return function Benchmarks(controller) {
    var startCounter;

    return [
      {
        name: "atoms",
        numeric: true,
        run: function(done) {
          done(controller.model.getNumberOfAtoms());
        }
      },
      {
        name: "temperature",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(controller.model.get("temperature"));
        }
      },
      {
        name: "engine (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          // warmup
          controller.model.start();
          setTimeout(function() {
            controller.model.stop();

            performance.collectData(true);
            startCounter = controller.model.stepCounter();

            setTimeout(function() {
              // actual fps calculation
              controller.model.start();
              setTimeout(function() {
                controller.model.stop();

                performance.collectData(false);
                done(performance.getAvgTime("engine"));

              }, TEST_TIME);
            }, 100);
          }, WARMUP_TIME);
        }
      },
      {
        name: "JS rendering (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(performance.getAvgTime("js-rendering"));
        }
      },
      {
        name: "tick (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(performance.getAvgTime("tick"));
        }
      },
      {
        name: "gap b/w frames (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(performance.getAvgTime("gap"));
        }
      },
      {
        name: "fps",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done((controller.model.stepCounter() - startCounter) * 1000 / TEST_TIME);
        }
      }
    ];
  };
});

/*global
  define
*/
/*jslint onevar: true*/
define('models/md2d/controllers/controller',['require','common/controllers/model-controller','models/md2d/models/modeler','models/md2d/views/view','models/md2d/controllers/scripting-api','models/md2d/benchmarks/benchmarks'],function (require) {
  // Dependencies.
  var ModelController   = require("common/controllers/model-controller"),
      Model             = require('models/md2d/models/modeler'),
      ModelContainer    = require('models/md2d/views/view'),
      ScriptingAPI      = require('models/md2d/controllers/scripting-api'),
      Benchmarks        = require('models/md2d/benchmarks/benchmarks');

  return function (modelUrl, modelOptions, interactiveController) {
    return new ModelController(modelUrl, modelOptions, interactiveController,
                               Model, ModelContainer, ScriptingAPI, Benchmarks);
  };
});

/*global define: false */

define('models/signal-generator/metadata',[],function() {

  return {
    mainProperties: {
      type: {
        defaultValue: "signal-generator",
        immutable: true
      },
      frequency: {
        defaultValue: 1,
        unitType: "frequency",
        propertyChangeInvalidates: true
      },
      modelSampleRate: {
        defaultValue: 60,
        propertyChangeInvalidates: true
      },
      timeScale: {
        defaultValue: 1,
        unitType: "time",
        propertyChangeInvalidates: true
      },
      sampleBatchLength: {
        defaultValue: 1,
        propertyChangeInvalidates: false
      },
      useDuration: {
        defaultValue: 'codap',
        storeInTickHistory: false,
        validate: function(value) {
          if (value === true || value === false || value === 'codap') {
            return value;
          }
          throw new Error("Invalid 'useDuration' value: " + value);
        },
      },
      requestedDuration: {
        defaultValue: null,
        storeInTickHistory: false
      }
    },
    viewOptions: {
      showClock: {
        defaultValue: true,
        propertyChangeInvalidates: false
      },
      controlButtons: {
        defaultValue: "play_reset",
        propertyChangeInvalidates: false
      },
      controlButtonStyle: {
        defaultValue: "video",
        propertyChangeInvalidates: false,
        serialize: false
      }
    }
  };
});

/*global define: false */

define('models/signal-generator/modeler',['require','common/lab-modeler-mixin','models/signal-generator/metadata'],function(require) {

  var LabModelerMixin         = require('common/lab-modeler-mixin'),
      metadata                = require('models/signal-generator/metadata'),

      unitsDefinition = {
        units: {
          time: {
            name: "second",
            pluralName: "seconds",
            symbol: "s"
          },
          frequency: {
            name: "Hertz",
            pluralName: "Hertz",
            symbol: "Hz"
          },
          angle: {
            name: "radian",
            pluralName: "radians",
            symbol: "rad"
          }
        }
      };

  return function Model(initialProperties) {
    var customSetters = {
          // Ensure that phase + (time * angular frequency) remains unchanged when the frequency changes.
          // This makes for continuous signals.
          frequency: function (newFrequency) {
            if (lastFrequency !== undefined) {
              phase = constrain(phase + 2 * Math.PI * (lastFrequency - newFrequency) * model.properties.time);
            }
            lastFrequency = newFrequency;
          }
        },

        labModelerMixin = new LabModelerMixin({
          metadata: metadata,
          setters: customSetters,
          unitsDefinition: unitsDefinition,
          initialProperties: initialProperties
        }),
        dispatch = labModelerMixin.dispatchSupport,

        lastFrequency,
        phase = 0,
        times = [],
        stepCounter = 0,
        timesIndex = -1,
        model;

    function constrain(angle) {
      return angle - 2 * Math.PI * Math.floor(angle / (2 * Math.PI));
    }

    function angle(time) {
      return constrain(phase + 2 * Math.PI * model.properties.frequency * time);
    }

    function getIndexWithDefault(array, index, defaultValue) {
      return index < 0 ? defaultValue : array[index];
    }

    model = {

      tick: function () {
        var intervalLength = 1000 / model.properties.modelSampleRate;
        var lastTime = getIndexWithDefault(times, times.length - 1, 0);

        times.push(lastTime + 0.001 * intervalLength * model.properties.timeScale);

        stepCounter++;

        if (stepCounter % model.properties.sampleBatchLength === 0) {
          while (timesIndex < times.length - 1) {
            timesIndex++;
            model.updateAllOutputProperties();
            dispatch.tick();
          }
        }
      },

      stepCounter: function() {
        return stepCounter;
      },

      reset: function() {
        dispatch.reset();
        // TODO
      }

    };

    dispatch.addEventTypes("tick");
    dispatch.addEventTypes('reset');

    labModelerMixin.mixInto(model);

    model.on('play.model', model.makeInvalidatingChange);
    model.on('stop.model', model.makeInvalidatingChange);

    model.defineOutput('isPlayable', {
      label: "Playable"
    }, function() {
      return model.isReady && model.isStopped();
    });

    model.defineOutput('hasPlayed', {
      label: "has Played"
    }, function() {
      return model.hasPlayed;
    });

    model.defineOutput('timePerTick', {
      label: "Model time per tick",
      unitType: 'time',
      format: '.2f'
    }, function() {
      return 1 / model.properties.modelSampleRate * model.properties.timeScale;
    });

    model.defineOutput('time', {
      label: "Time",
      unitType: 'time',
      format: '.2f'
    }, function() {
      return getIndexWithDefault(times, timesIndex, 0);
    });

    model.defineOutput('displayTime', {
      label: "Time",
      unitType: 'time',
      format: '.2f'
    }, function() {
      return getIndexWithDefault(times, timesIndex, 0);
    });

    model.defineOutput('signalValue', {
      label: "Signal Value",
      format: '.2f'
    }, function() {
      var time = getIndexWithDefault(times, timesIndex, 0);
      return Math.cos(angle(time));
    });

    model.defineOutput('angle', {
      label: "Angle",
      unitType: 'angle',
      format: '.2f'
    }, function() {
      var time = getIndexWithDefault(times, timesIndex, 0);
      return angle(time);
    });

    return model;
  };
});

/*global define: false $: false */

/**
  For use with models that do not need an associated view, but which (at least for now) are required
  by the common ModelController to have a ModelContainer which has an $el property and which
  responds to certain methods.
*/

define('common/views/null-model-view',[],function() {
  return function() {
    return  {
      $el: $("<div id='model-container' class='container'/>"),
      getHeightForWidth: function() { return 0; },
      resize: function() {},
      repaint: function() {},
      bindModel: function() {},
      setup: function() {},
      update: function() {}
    };
  };
});

/*global define */

define('models/signal-generator/controller',['require','common/controllers/model-controller','models/signal-generator/modeler','common/views/null-model-view'],function (require) {
  // Dependencies.
  var ModelController   = require('common/controllers/model-controller'),
      Model             = require('models/signal-generator/modeler'),
      ModelContainer    = require('common/views/null-model-view'),
      ScriptingAPI      = function() {};

  return function (modelUrl, modelOptions, interactiveController) {
    return new ModelController(modelUrl, modelOptions, interactiveController,
                               Model, ModelContainer, ScriptingAPI);
  };
});

/*global define: false */

define('models/iframe/metadata',[],function() {

  return {
    mainProperties: {
      type: {
        defaultValue: "iframe-model",
        immutable: true
      },
      url: {
        immutable: true
      }
    },
    viewOptions: {
      aspectRatio: {
        defaultValue: 1.55
      },
      showClock: {
        defaultValue: false,
        propertyChangeInvalidates: false
      },
      controlButtons: {
        defaultValue: "reset",
        propertyChangeInvalidates: false
      }
    }
  };
});

/*global define: false */

/*
  Options for starting on property support:
  - add a hard-coded outputProperty that reads from a cached value, and start a timer to trigger
    the method that notifies all outputProperty listereners
  - add a hard-coded parameter, that we update based on a property listener

  Possible next steps:
  - make paramaters, outputProperties, or mainProperties be more dynamic so they are 'defined'
    in the iframe-model json definition.
  - create a way for the iframe'd html to send the property definition, which the model then uses
    to add the properties to itself. This will keep the properties in the same place as the model
    and possible enable the model to more easily be used in a different container other than Lab.

  Possible problems:
  - properites will need to be 'cached' in the interactive window, so that getters will work. the
    property system already has support for caching. But it means that all properties are going to
    have to be sent 'over the wire' whenever they are changed inorder for getters to work. If
    the model json or interactive defines which properties are needed then this might not be a problem,
    but if the properties are provided by the iframe'd html, it won't know what is needed so it will
    send all properties.
*/
define('models/iframe/modeler',['require','common/lab-modeler-mixin','models/iframe/metadata'],function(require) {

  var LabModelerMixin = require('common/lab-modeler-mixin');
  var metadata        = require('models/iframe/metadata');

  function IFrameModel(initialProperties) {
    var labModelerMixin = new LabModelerMixin({
      metadata: metadata,
      initialProperties: initialProperties,
      usePlaybackSupport: false
    });

    labModelerMixin.mixInto(this);
    // Use custom .set() instead of one provided by property support module.
    // Custom version is also a bit looser, it lets you define properties dynamically.
    // Setter of newly added property will post message to iframe model.
    this.set = customSet;

    this._phone = null;
    this._stopped = true;
    this._initialProperties = initialProperties;
    this._propertySupport = labModelerMixin.propertySupport;
    this._dispatch = labModelerMixin.dispatchSupport;
    this._stepCounter = 0;
    // Special flag that makes sure we do not end up in an infinite loop
    // of "set" -> "properties" -> "set" -> ... calls.
    this._propertyUpdateComingFromIframe = false;

    // Custom properties defined by the model in the iframe; values are passed using 'outputs'
    // or 'tick' messages.
    this._iframeOutputs = {};

    // The default model controller asumes 'tick' is a defined event
    // the playback controller assumes 'play' and 'stop' are defined even if the viewOptions
    // disable these buttons
    // the outer iframe in the interactives browser expects a 'reset', 'stepForward', 'stepBack' event type
    this._dispatch.addEventTypes('tick', 'tickStart', 'tickEnd', 'play', 'stop', 'reset', 'stepForward', 'stepBack', 'log');
  }

  function customSet(key, value) {
    var context = this;
    // Multiple keys provided.
    if (typeof key !== 'string') {
      var hash = key;
      Object.keys(hash).forEach(function (key) {
        context.set(key, hash[key]);
      });
      return;
    }
    // Single key provided.
    if (!this.hasProperty(key)) {
      this._propertySupport.defineProperty(key, {
        type: "mainProperty",
        writable: true,
        set: function (value) {
          if (!context._propertyUpdateComingFromIframe) {
            // Make sure that it's not a result of 'properties' message sent by iframe model.
            // Otherwise, we could end up in an infinite loop of property updates.
            context._phone.post("set", {name: key, value: value});
          }
        },
        includeInHistoryState: false,
        beforeSetCallback: this._propertySupport.invalidatingChangePreHook,
        afterSetCallback: this._propertySupport.invalidatingChangePostHook
      });
    }

    // This will call setter defined above and post message to iframe.
    this.properties[key] = value;
  }

  // If property is not defined in metadata, it means it's a custom property related
  // to specific iframe model. Use custom set method to define it and send message to iframe.
  // labModelerMixin will automatically define and set only properties that are present in metadata.
  IFrameModel.prototype._defineModelSpecificProperties = function() {
    var initialProperties = this._initialProperties;
    var context = this;
    Object.keys(initialProperties).forEach(function (name) {
      if (!metadata.mainProperties[name] && !metadata.viewOptions[name]) {
        context.set(name, initialProperties[name]);
      }
    });
  };

  /**************** public methods ****************/

  IFrameModel.prototype.start = function () {
    this._phone.post({type: 'play'});
    return this;
  };

  IFrameModel.prototype.stop = function () {
    // stop the iframe'd model
    // TODO #1 we might want to set stopped to true here.
    // in the playback-support for other models isStopped checks for 'stopRequest'
    // which is set as soon as stop is called.
    // TODO #2 we are not notifying about the stop event until we hear from
    // the model that it is stopped. We should check if that is best approach.
    // It is possible that some data events will be sent out even after stop
    // is called so it might be best to leave this as is. Because of javascript single
    // threading it seems unlikely this would be a problem in a regular model.
    this._phone.post({type: 'stop'});
    return this;
  };

  IFrameModel.prototype.restart = function () {
    this.stop();
    this.start();
    return this;
  };

  IFrameModel.prototype.isStopped = function () {
    return this._stopped;
  };

  IFrameModel.prototype.serialize = function () {
    // TODO: obviously it's not a real serialization.
    return this._initialProperties;
  };

  Object.defineProperty(IFrameModel.prototype, "iframePhone", {
    set: function (val) {
      this._phone = val;
      this._addListeners();
      this._defineModelSpecificProperties();
    },
    get: function () {
      return this._phone;
    }
  });

  IFrameModel.prototype.stepCounter = function () {
    return this._stepCounter;
  };

  IFrameModel.prototype.iframeOutput = function (name) {
    return this._iframeOutputs[name];
  };

  /**************** private methods ****************/

  IFrameModel.prototype._updateOutputs = function (outputs) {
    var context = this;
    this.makeInvalidatingChange(function () {
      Object.keys(outputs).forEach(function (key) {
        context._iframeOutputs[key] = outputs[key];
      });
    });
  };

  IFrameModel.prototype._updatePropertiesFromIframe = function (propertiesHash) {
    this._propertyUpdateComingFromIframe = true;
    this.set(propertiesHash);
    this._propertyUpdateComingFromIframe = false;
  };

  IFrameModel.prototype._addListeners = function () {
    var context = this;
    this._phone.addListener('play.iframe-model', function () {
      context._stopped = false;
      // notify that we are playing
      context._dispatch.play();
    });
    this._phone.addListener('stop.iframe-model', function () {
      context._stopped = true;
      // notify that we are stopped
      context._dispatch.stop();
    });

    this._phone.addListener('properties', function(content) {
      context._updatePropertiesFromIframe(content);
    });

    this._phone.addListener('outputs', function(content) {
      context._updateOutputs(content);
    });

    this._phone.addListener('tick', function (content) {
      context._stepCounter++;
      // Support outputs and/or properties update within 'tick' message.
      if (content && content.outputs) {
        context._updateOutputs(content.outputs);
      }
      if (content && content.properties) {
        context._updatePropertiesFromIframe(content.properties);
      }
      context._dispatch.tick();
    });

    // Support logging provided by inner frame. Dispatch 'log' event,
    // as LogController listens to this event type.
    this._phone.addListener('log', function (content) {
      context._dispatch.log(content.action, content.data);
    });
  };

  return IFrameModel;
});

/*global $, define: false */
define('models/iframe/iframe-container',['require','iframe-phone'],function(require) {
  var iframePhone = require('iframe-phone');

  return function IFrameContainer(model) {
    var _model = model,
        $el = $("<div id='model-container' class='container'></div>");

    function addIFrame(model) {
        $el.find('#iframe-model').remove();
        var $iframe = $("<iframe id='iframe-model' " +
                        "style='width:100%;height:100%' src='" + model.get('url') + "'>" +
                        "</iframe>");
        $el.append($iframe);

        var phone = new iframePhone.ParentEndpoint($el.find('#iframe-model')[0]);
        // Simply assume that when iframe is removed from DOM (e.g. due to model reload or
        // interactive reload), we should also disconnect iframe phone.
        // If we leave it connected, handler will be still trying to process all incoming messages
        // and it would fail, as its iframe wouldn't be attached to DOM anymore (so its
        // .contentWindow will be equal to null).
        // Note that 'destroyed' is a custom event specified in jquery-plugins.js!
        $iframe.on('destroyed', function () {
          phone.disconnect();
        });

        model.iframePhone = phone;
    }

    addIFrame(model);

    return  {
      $el: $el,
      getHeightForWidth: function(width) {
        return width / _model.get('aspectRatio');
      },
      resize: function() {},

      // This method is called in a few different cases:
      //  - when a interactive is loaded and the same modelController is reused
      //  - when scriptAPI.reset is called
      // Note: it is _not_ called the first time the modelController is created
      // Whether the element is actually in the page's DOM varies depending on how
      // this is called. However, the $el object will be reused in all cases so
      // manipulating this seems to work.
      // currently the safest thing is to remove and add a new iframe each time
      // this causes the iframe to reload even if the url is the same.
      // This can make the reset be slow, so in the reset case it would be better to optimize
      // this to send some reset event to the iframe instead.
      bindModel: function(newModel) {
        addIFrame(newModel);
        _model = newModel;
      },

      // this is called after the element has been added to the DOM
      // the element is not re-added to the DOM during a scriptAPI.reset()
      setup: function() {},
      update: function() {},
      repaint: function() {}
    };
  };
});

define('models/iframe/scripting-api',[],function () {
  /**
    Define the model-specific Sensor scripting API used by 'action' scripts on interactive elements.

    The universal Interactive scripting API is extended with the properties of the
    object below which will be exposed to the interactive's 'action' scripts as if
    they were local vars. All other names (including all globals, but excluding
    Javascript builtins) will be unavailable in the script context; and scripts
    are run in strict mode so they don't accidentally expose or read globals.

    @param: parent Common Scripting API
  */
  return function IframeScriptingAPI (parent) {

    // Overwrite original .bindModel method of parent Scripting API.
    // Iframe model can send a "registerScriptingAPIFunc" message to register new Scripting API
    // function that in fact would be a shortcut for .post() call.
    var orgBindModel = parent.bindModel;
    parent.bindModel = function() {
      orgBindModel.apply(parent, arguments);

      parent.model.iframePhone.addListener("registerScriptingAPIFunc", function (name) {
        if (parent.api[name] != null) return;
        parent.api[name] = function (content) {
          parent.model.iframePhone.post(name, content);
        };
      });
    };

    return {
      /**
       * Posts a custom message to iframe model.
       * @param  {string} type    message type
       * @param  {any}    content message content
       */
      post: function resetModel(type, content) {
        parent.model.iframePhone.post(type, content);
      },

      /**
       * Returns value of an output defined by iframe model.
       * Note that iframe model decides how often output is updated.
       * This function doesn't ask iframe about current value, but just immediately returns
       * the last value which is stored.
       * @param {string} name   output name
       */
      iframeOutput: function iframeOutput(name) {
        return parent.model.iframeOutput(name);
      }
    };
  };
});

/*global define */

define('models/iframe/controller',['require','common/controllers/model-controller','models/iframe/modeler','models/iframe/iframe-container','models/iframe/scripting-api'],function (require) {
  // Dependencies.
  var ModelController   = require('common/controllers/model-controller'),
      Model             = require('models/iframe/modeler'),
      ModelContainer    = require('models/iframe/iframe-container'),
      ScriptingAPI      = require('models/iframe/scripting-api');

  return function (modelUrl, modelOptions, interactiveController) {
    return new ModelController(modelUrl, modelOptions, interactiveController,
                               Model, ModelContainer, ScriptingAPI);
  };
});

/*global define: false */

define('models/sensor/metadata',[],function() {

  return {
    mainProperties: {
      type: {
        defaultValue: "sensor",
        immutable: true
      },
      sensorType: {
        defaultValue: null
      },
      samplesPerSecond: {
        readOnly: true
      },
      collectionTime: {
        defaultValue: null
      },
      tareValue: {
        defaultValue: 0
      }
    },
    viewOptions: {
      showClock: {
        defaultValue: true,
        propertyChangeInvalidates: false
      },
      controlButtons: {
        defaultValue: "play_reset",
        propertyChangeInvalidates: false
      },
      controlButtonStyle: {
        defaultValue: "video",
        propertyChangeInvalidates: false,
        serialize: false
      }
    }
  };
});

!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define('sensor-applet',e):"undefined"!=typeof window?window.sensorAppletInterface=e():"undefined"!=typeof global?global.sensorAppletInterface=e():"undefined"!=typeof self&&(self.sensorAppletInterface=e())}(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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.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(require,module,exports){
var miniClass = require('./mini-class');
var SensorApplet = require('./sensor-applet');

module.exports = {
  GoIO: miniClass.extendClass(SensorApplet, {
    deviceType: 'golink',
    deviceSpecificJars: [ 'sensor-vernier', 'goio-jna']
  }),

  LabQuest: miniClass.extendClass(SensorApplet, {
    deviceType: 'labquest',
    deviceSpecificJars: [ 'sensor-vernier', 'labquest-jna']
  })
};

},{"./mini-class":4,"./sensor-applet":6}],2:[function(require,module,exports){
'use strict';

var util = require('util');

function errorConstructor(message) {
  /*jshint validthis: true*/
  Error.call(this); //super constructor
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, this.constructor); //super helper method to include stack trace in error object
  }

  this.name = this.constructor.name; //set our function’s name as error name.
  this.message = message; //set the error message
}

function JavaLoadError() {
  errorConstructor.apply(this, arguments);
}
util.inherits(JavaLoadError, Error);

function AppletInitializationError() {
  errorConstructor.apply(this, arguments);
}
util.inherits(AppletInitializationError, Error);

function SensorConnectionError() {
  errorConstructor.apply(this, arguments);
}
util.inherits(SensorConnectionError, Error);

function AlreadyReadingError() {
  errorConstructor.apply(this, arguments);
}
util.inherits(AlreadyReadingError, Error);

module.exports = {
  JavaLoadError: JavaLoadError,
  AppletInitializationError: AppletInitializationError,
  AlreadyReadingError: AlreadyReadingError,
  SensorConnectionError: SensorConnectionError
};

},{"util":11}],3:[function(require,module,exports){
// This file is built by the update-timestamp.rb script do not edit it directly
module.exports = "20140417.203729";
},{}],4:[function(require,module,exports){
'use strict';

/**

  mini-class.js

  Minimalist classical-OO style inheritance for JavaScript.
  Adapted from CoffeeScript and SproutCore.

  Richard Klancer, 7-23-2012
*/

function mixin(dest, src) {
  var hasProp = {}.hasOwnProperty,
      key;

  for (key in src) {
    if (hasProp.call(src, key)) {
      dest[key] = src[key];
    }
  }
}

//
// Remember that "classes" are just constructor functions that create objects, and that the
// constructor function property called `prototype` is used to define the prototype object
// (aka the __proto__ property) which will be assigned to instances created by the constructor.
// Properties added to the prototype object of a constructor effectively become the instance
// properties/methods of objects created with that constructor, and properties of the prototype
// of the prototype are effectively "superclass" instance properties/methods.
//
// See http://javascriptweblog.wordpress.com/2010/06/07/understanding-javascript-prototypes/
//

/**
  Assuming Child, Parent are classes (i.e., constructor functions):
    1. Copies the properties of the Parent constructor to the Child constructor (These can be
       considered "class properties"/methods, shared among all instances of a class.)
    2. Adds Parent's prototype to Child's prototype chain.
    3. Adds Parent's prototype to the '__super__' property of Child.
*/
function extend(Child, Parent) {

  // First, copy direct properties of the constructor object ("class properties") from Parent to
  // Child.
  mixin(Child, Parent);

  // First step in extending the prototype chain: make a throwaway constructor, whose prototype
  // property is the same as the Parent constructor's prototype property. Objects created by
  // calling `new PrototypeConstructor()` will have the *same* prototype object as objects created
  // by calling `new Parent()`.
  function PrototypeConstructor() {
    this.constructor = Child;
  }
  PrototypeConstructor.prototype = Parent.prototype;

  // Now use PrototypeConstructor to extend the prototype chain by one link.
  // That is, use PrototypeConstructor to make a new *object* whose prototype object
  // (__proto__ property) is Parent.prototype, and assign the object to the Child constructor's
  // prototype property. This way, objects created by calling "new Child()"
  // will have a prototype object whose prototype object in turn is Parent.prototype.
  Child.prototype = new PrototypeConstructor();

  // Assign the prototype used by objects created by Parent to the __super__ property of Child.
  // (This property can be accessed within a Child instance as `this.constructor.__super__`.)
  // This allows a Child instance to look "up" the prototype chain to find instances properties
  // defined in Parent that are overridden in Child (i.e., defined on Child.prototype)
  Child.__super__ = Parent.prototype;
}

/**
  Defines a "class" whose instances will have the properties defined in `prototypeProperties`:
    1. Creates a new constructor, which accepts a list of properties to be copied directly onto
       the instance returned by the constructor.
    2. Adds the properties in `prototypeProperties` to the prototype object shared by instances
       created by the constructor.
*/
function defineClass(prototypeProperties) {
  function NewConstructor(instanceProperties) {
    mixin(this, instanceProperties);
  }
  mixin(NewConstructor.prototype, prototypeProperties);
  return NewConstructor;
}

/**
  Given ParentClass, return a new class which is ParentClass extended by childPrototypeProperties
*/
function extendClass(ParentClass, childPrototypeProperties) {
  function ChildConstructor(instanceProperties) {
    mixin(this, instanceProperties);
  }
  // Extend ParentClass first so childPrototypeProperties override anything defined in ParentClass
  extend(ChildConstructor, ParentClass);
  mixin(ChildConstructor.prototype, childPrototypeProperties);
  return ChildConstructor;
}

module.exports = {
  defineClass: defineClass,
  extendClass: extendClass,
  mixin: mixin
};
},{}],5:[function(require,module,exports){
'use strict';

/**
  Basic event-emitter functionality to mixin to other classes.

  TODO: needs explicit tests (is currently *implicitly* tested by sensor-applet_spec).
*/
module.exports = {

  on: function(evt, cb) {
    if (!this._ee_listeners) {
      this._ee_listeners = {};
    }
    if (!this._ee_listeners[evt]) {
      this._ee_listeners[evt] = [];
    }

    this._ee_listeners[evt].push(cb);
  },

  emit: function(evt) {
    var args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];

    if (this._ee_listeners && this._ee_listeners[evt]) {
      for (var i = 0, len = this._ee_listeners[evt].length; i < len; i++) {
        this._ee_listeners[evt][i].apply(null, args);
      }
    }
  },

  removeListener: function(evt, listener) {
    if (this._ee_listeners && this._ee_listeners[evt]) {
      for (var i = 0, len = this._ee_listeners[evt].length; i < len; i++) {
        if (this._ee_listeners[evt][i] === listener) {
          this._ee_listeners[evt].splice(i, 1);
        }
      }
    }
  },

  removeListeners: function(evt) {
    if (!evt) {
      this._ee_listeners = {};
    } else {
      if (this._ee_listeners) {
        this._ee_listeners[evt] = [];
      }
    }
  }
};

},{}],6:[function(require,module,exports){
'use strict';

var miniClass = require('./mini-class');
var EventEmitter = require('./mini-event-emitter');
var errors = require('./errors');
var jarsTimestamp = require('./jars-timestamp');
var SensorApplet;

function AppletWaiter(){
  var _timer = null,
      _opts = null;

  this.handleCallback = function (){
    console.log("handling callback from applet");
    // this is asynchronous because it will be called by Java
    setTimeout(function (){
      if (_timer === null) {
        console.log("applet called callback after timer expired");
        return;
      }
      window.clearInterval(_timer);
      _timer = null;
      _opts.success();
    }, 5);
  };

  this.wait = function(options){
    var attempts = 0,
        maxAttempts = options.times;

    _opts = options;

    _timer = window.setInterval(function() {
      attempts++;

      if (attempts > maxAttempts) {
        // failure
        window.clearInterval(_timer);
        _timer = null;
        options.fail();
      }
    }, options.interval);
  };
}

/**
  events:
    data
    deviceUnplugged
    sensorUnplugged

  states:
    not appended
    test applet appended
    appended
    applet ready
    stopped
    started

  api methods:
    getState          useful for tracking initialization
    append(callback)  initialize applet, checking for Java with test applet
    readSensor        read a single value
    start             start a collection
    stop              stop collection
    remove            remove applet

*/
SensorApplet = miniClass.defineClass({
  // Before appending the applet, set this value with the path to an object that will receive applet callbacks.
  listenerPath: '',

  // Before appending the applet this should be set to a array of definitions from
  // senor-applet/sensor-definitions.js
  // FIXME: these should be updated to be device independent
  sensorDefinitions: null,

  // Before appending the applet, set this to the path or URL where jars can be found
  codebase: '',

  // supported values are:
  //  "labquest"
  //  "golink"
  deviceType: '',

  appletId:     'sensor-applet',
  classNames:   'applet sensor-applet',

  jars:     ['jna', 'sensor', 'sensor-applets'],

  deviceSpecificJarUrls: [],

  code:         'org.concord.sensor.applet.SensorApplet',

  testAppletReadyInterval: 100,

  getArchiveValue: function(jars) {
    var jarUrls = [];
    for(var i=0; i<jars.length; i++){
      jarUrls[i] = jars[i] + '.jar';
    }
    return jarUrls.join(', ');
  },

  getHTML: function() {
    /*jshint indent: false*/
    var allJars = this.jars.concat(this.deviceSpecificJars);

    return [
     '<applet ',
       'id="',       this.appletId, '" ',
       'class="',    this.classNames, '" ',
       'archive="',  this.getArchiveValue(allJars), '" ',
       'code="',     this.code,             '" ',
       'codebase="', this.codebase, '/', jarsTimestamp, '" ',
       'width="1px" ',
       'height="1px" ',
       'MAYSCRIPT="true" ',
     '>',
        '<param name="MAYSCRIPT" value="true" />',
        '<param name="evalOnInit" value="' + this.listenerPath + '.appletIsReadyCallback()" />',
        '<param name="permissions" value="all-permissions" />',
        '<param name="java_arguments" value="-Djnlp.packEnabled=true"/>',
      '</applet>'
    ].join('');
  },

  getTestAppletHTML: function() {
    /*jshint indent: false*/
    return [
     '<applet ',
       'id="',       this.appletId,         '-test-applet" ',
       'class="applet test-sensor-applet" ',
       'code="org.concord.sensor.applet.DetectionApplet" ',
       'archive="', this.getArchiveValue(['sensor-applets']), '" ',
       'codebase="', this.codebase, '/', jarsTimestamp, '" ',
       'width="150px" ',
       'height="150px" ',
       'style="position: absolute; ',
              'left: ' + ($('body').width() / 2 - 75) +'px; ',
              'top: ' + ($('body').height() / 2 - 75) +'px;" ',
       'MAYSCRIPT="true" ',
     '>',
        '<param name="MAYSCRIPT" value="true" />',
        '<param name="evalOnInit" value="' + this.listenerPath + '.testAppletIsReadyCallback()" />',
        '<param name="permissions" value="all-permissions" />',
        '<param name="java_arguments" value="-Djnlp.packEnabled=true"/>',
      '</applet>'
    ].join('');
  },

  /**
    Passes true to the callback if the correct device type is connected.
  */
  isSensorConnected: function(callback) {
    var self = this, nextCallback, nextCallbackIdx;
    setTimeout(function() {
      nextCallback = function(connected) {
        // Note this appears only to return a meaningful result when first called. After that, it
        // returns the same value for a given deviceType, even if the device has been unplugged from
        // the USB port.
        if(!connected) {
          callback.call(self, false);
        } else {
          nextCallback = function() {
            var attachedSensors = JSON.parse(self.appletInstance.getCachedAttachedSensors());
            if (attachedSensors) {
              // FIXME we should use the applet configure method to check if the right sensors are attached
              // instead of doing this comparison here
              // For now this is skipped if there is more than one sensorDefinition
              if(self.sensorDefinitions.length === 1) {
                for (var i = 0; i < attachedSensors.length; i++) {
                  if (self.appletInstance.getTypeConstantName(attachedSensors[i].type) ===
                        self.sensorDefinitions[0].typeConstantName) {
                    callback.call(self, true);
                    return;
                  }
                }
                callback.call(self, false);
              } else {
                callback.call(self, true);
              }
            } else {
              callback.call(self, false);
            }
          };
          nextCallbackIdx = self.registerCallback(nextCallback);
          self.appletInstance.getAttachedSensors(self.deviceType, ""+nextCallbackIdx);
        }
      };
      nextCallbackIdx = self.registerCallback(nextCallback);
      self.appletInstance.isInterfaceConnected(self.deviceType, ""+nextCallbackIdx);
    });
  },

  _state: 'not appended',

  getState: function() {
    return this._state;
  },

  /**
    Append the applet to the DOM, and call callback when either:

      (1) The applet is configured and ready, with the correct device attached (it is ready to
          start collecting data immediately). The SensorApplet instance will be in the 'stopped'
          state.

      or:

      (2) An error occurs in the initialization process. An error object will be passed as the
          first argument to the callback (Node.js style).

      Currently, we detect three kinds of errors:

        * The Java plugin does not appear to be working (we time out waiting for a callback from
          our test applet). In this case, application code may want to remove the applet and try
          calling 'append' again later.

        * The sensor applet was appended, but never initializes (we time out waiting for a callback
          from the sensor applet).  In this case, application code may want to remove the applet
          and try calling 'append' again later.

        * The sensor applet reports that the wrong sensor type is attached. In this case,
          the applet is known to be loaded, and the application code may want to notify the user,
          and call 'initializeSensor' when the user indicates the sensor is plugged in. If
          If the callback is called with a null argument, the applet is ready to collect data.
  */
  append: function($loadingParent, callback) {
    if (this.getState() !== 'not appended') {
      throw new Error("Can't call append() when sensor applet has left 'not appended' state");
    }
    console.log("appending test applet");
    this.$testAppletContainer = this._appendHTML(this.appletId + "-test-applet-container",
                                                 this.getTestAppletHTML(),
                                                 $loadingParent);
    this._state = 'test applet appended';
    this._waitForTestApplet();
    this._appendCallback = callback;
  },

  _appendHTML: function(containerId, html, $parent) {
    var appletContainer = $('#' + containerId );

    if(!appletContainer.length){
      appletContainer = $("<div id='" + containerId + "'/>").appendTo($parent);
    }

    // using .append() actually creates some sort of internal reference to the applet,
    // which can cause problems calling applet methods later. Using .html() seems to avoid this.
    appletContainer.html(html);
    return appletContainer;
  },

  _testAppletWaiter: new AppletWaiter(),
  // this will be called by the test applet once it is initialized
  testAppletIsReadyCallback: function () {
    this._testAppletWaiter.handleCallback();
  },

  _waitForTestApplet: function() {
    var self = this;
    this._testAppletWaiter.wait({
      times: 30,
      interval: 1000,
      success: function() {
        self.$appletContainer = self._appendHTML(self.appletId + "-container",
                                                 self.getHTML(),
                                                 $('body'));
        self._state = 'appended';
        self._waitForApplet();
      },
      fail: function () {
        self._appendCallback(new errors.JavaLoadError("Timed out waiting for test applet to initialize."));
      }
    });
  },

  _appletWaiter: new AppletWaiter(),
  // this will be called by the applet once it is initialized
  appletIsReadyCallback: function () {
    this._appletWaiter.handleCallback();
  },

  _waitForApplet: function() {
    var self = this;
    this._appletWaiter.wait({
      times: 30,
      interval: 1000,
      success: function() {
        var requests = [];
        // remove test applet
        self.$testAppletContainer.html("");
        if (self.getState() === 'appended') {
          self._state = 'applet ready';
        }

        self.appletInstance = $('#'+self.appletId)[0];

        for(var i=0; i<self.sensorDefinitions.length; i++){
          // Get a SensorRequest object for this measurement type
          requests[i] =
            self.appletInstance.getSensorRequest(self.sensorDefinitions[i].measurementType);
        }

        // Try to initialize the sensor for the correct device and measurement type (e.g., goio,
        // distance). Java will callback to initSensorInterfaceComplete on success or error.
        self.appletInstance.initSensorInterface(self.listenerPath, self.deviceType, requests);
      },
      fail: function () {
        self._appendCallback(new errors.AppletInitializationError("Timed out waiting for sensor applet to initialize."));
      }
    });
  },

  // callback: function(error, values) {}
  readSensor: function(callback) {
    var self = this;
    if (this.getState() === 'reading sensor') {
      console.log("Already reading sensor in another thread...");
      callback.call(this, new errors.AlreadyReadingError("Already reading sensor in another thread"), null);
      return;
    }

    if (this.getState() !== 'stopped') {
      callback.call(this, new Error("Tried to read the sensor value from non-stopped state '" + this.getState() + '"'), null);
      return;
    }

    // because of IE multi threading applet behavior we need to track our state before calling
    // the applet
    this._state = 'reading sensor';
    this.isSensorConnected(function(connected) {
      if (connected) {
        var valuesCallback = function(values) {
          self._state = 'stopped';
          if (!values || values.length === 0) {
            callback.call(self, new Error("readSensor: no sensor values to report"), null);
          } else {
            callback.call(self, null, values);
          }
        };
        var callbackIdx = self.registerCallback(valuesCallback);
        self.appletInstance.getConfiguredSensorsValues(self.deviceType, ""+callbackIdx);
      } else {
        self._state = 'stopped';
        callback.call(self, new errors.SensorConnectionError("readSensor: sensor is not connected"), null);
      }
    });
  },

  // callback: function(error, isStarted) {}
  start: function(callback) {
    var self = this;
    if (this.getState() === 'reading sensor') {
      console.log("start called while waiting for a sensor reading");

      // because of IE multi threading we might we waiting for a reading from the sensor still
      // so we try waiting for little while before giving up

      // this will cause a infinite loop of the applet blocks forever
      // however that is what happens in normal browsers anyhow
      setTimeout(function(){
        self.start(callback);
      }, 100);
      return;
    }

    if (this.getState() !== 'stopped') {
      if (callback) {
        setTimeout(function(){
          callback.call(this, new Error("Tried to start the applet from non-stopped state '" + this.getState() + '"'), false);
        }, 5);
      }
      return;
    }
    // in IE a slow call to an applet will result in other javascript being executed while waiting
    // for the applet. So we need to keep track of our state before calling Java.
    this._state = 'starting';

    // Remain in state 'stopped' if sensor is not connected. This is because we want the user to
    // be able to click 'start' again after plugging in the sensor. Changing to a different state
    // would require having some way to detect when to leave that state. We lack a way to
    // automatically detect that the sensor has been plugged in, and we don't want to force the
    // user to tell us.
    this.isSensorConnected(function(connected) {
      if (!connected) {
        self._state = 'stopped';
        if (callback) {
          callback.call(self, new errors.SensorConnectionError("Device reported the requested sensor type was not attached."), null);
        }
      } else {
        self.appletInstance.startCollecting();
        self._state = 'started';
        if (callback) {
          callback.call(self, null, true);
        }
      }
    });
  },

  stop: function() {
    if (this.getState() === 'started') {
      this._state = 'stopped';
      this.appletInstance.stopCollecting();
    }
  },

  remove: function() {
    if (this.getState() !== 'not appended') {
      if (this.$appletContainer) {
        this.$appletContainer.html("");
      }
      if (this.$testAppletContainer) {
        this.$testAppletContainer.html("");
      }
      this._state = 'not appended';
    }
  },

  // applet callbacks
  // we don't want to block the applet and we don't want to execute any code
  // in the callback thread because things can break if javascript calls back to Java in
  // a callback
  initSensorInterfaceComplete: function(success) {
    var self = this;
    setTimeout(function() {
      if(success){
        self._state = 'stopped';
        self._appendCallback(null);
        self._appendCallback = null;
      } else {
        // state should remain 'applet ready'
        self._appendCallback(new errors.SensorConnectionError("Device reported the requested sensor type was not attached."));
      }
    }, 5);
  },

  dataReceived: function(type, count, data) {
    var self = this,
        // FIXME this is inefficient to make a new object each time
        dataSample = [],
        numberOfSensors = this.sensorDefinitions.length;
    setTimeout(function () {
      data = data || [];
      for (var sampleIndex = 0; sampleIndex < count; sampleIndex++) {
        for (var i = 0; i < numberOfSensors; i++) {
          dataSample[i] = data[sampleIndex*numberOfSensors + i];
        }
        self.emit('data', dataSample);
      }
    }, 5);
  },

  deviceUnplugged: function() {
    var self = this;
    window.setTimeout(function() {
      self.emit('deviceUnplugged');
    }, 5);
  },

  sensorUnplugged: function() {
    var self = this;
    console.log("received sensorUnplugged message; deviceType = " + this.deviceType);
    // the model code is not currently handle this callback correctly
    return;

    window.setTimeout(function() {
      self.emit('sensorUnplugged');
    }, 10);
  },

  callbackTable: [],
  registerCallback: function(callback) {
    // TODO We might want to set up a "reaper" function to error the callback if a certain
    // amount of time passes and the callback hasn't been called.
    this.callbackTable.push(callback);
    return this.callbackTable.length-1;
  },

  handleCallback: function(index, value) {
    var callback, self = this;
    if (typeof(index) === "string" && this[index]) {
      // assume this is meant to call a direct method on this class instance
      callback = this[index];
    } else if (this.callbackTable[index]) {
      callback = this.callbackTable[index];
      this.callbackTable[index] = null;
    }

    if (callback) {
      // IE8 throws the error "Object expected" when 'undefined' is passed as the second argument
      // to Function.prototype.apply.
      value = value || [];
      setTimeout(function() {
        callback.apply(self, value);
      }, 5);
    }
  }
});

miniClass.mixin(SensorApplet.prototype, EventEmitter);

module.exports = SensorApplet;

},{"./errors":2,"./jars-timestamp":3,"./mini-class":4,"./mini-event-emitter":5}],7:[function(require,module,exports){
module.exports = {
  goMotion: {
    appletClass: 'GoIO',

    // Name of the measurement being made, for display in UI
    measurementName: "Distance",

    // measurement type, as accepted by applet's getSensorRequest method
    measurementType: 'distance',

    // measurement type, as returned by getTypeConstantName method.
    // The returned values are taken from the QUANTITY_* constants in the sensor project
    // See https://github.com/concord-consortium/sensor/blob/2da0693e4d92d8c107be802f29eab2688a83b26b/src/main/java/org/concord/sensor/SensorConfig.java
    typeConstantName: 'distance',

    // fully specified, readable name of the sensor: e.g., "GoIO pH Sensor"
    sensorName: "GoMotion",

    // readable name of the interface device the sensor connects to, e..g, "GoIO"
    deviceName: "GoMotion",

    samplesPerSecond: 20,
    tareable: true,
    minReading: -2,
    maxReading: 2,
    precision: 2,
    maxSeconds: 20
  },

  goTemp: {
    appletClass: 'GoIO',
    measurementName: "Temperature",
    measurementType: 'temperature',
    // QUANTITY_TEMPERATURE
    typeConstantName: 'temperature_wand',
    sensorName: "GoIO Temperature Sensor",
    deviceName: "GoIO",
    samplesPerSecond: 10,
    tareable: false,
    minReading: 0,
    maxReading: 40,
    maxSeconds: 20
  },

  goLinkTemperature: {
    appletClass: 'GoIO',
    measurementName: "Temperature",
    measurementType: 'temperature',
    // QUANTITY_TEMPERATURE
    typeConstantName: 'temperature',
    sensorName: "GoIO Temperature Sensor",
    deviceName: "GoIO",
    samplesPerSecond: 10,
    tareable: false,
    minReading: 0,
    maxReading: 40,
    maxSeconds: 20
  },

  goLinkLight: {
    appletClass: 'GoIO',
    measurementName: "Light Intensity",
    measurementType: 'light',
    // QUANTITY_LIGHT
    typeConstantName: 'light',
    sensorName: "GoIO Light Sensor",
    deviceName: "GoIO",
    samplesPerSecond: 10,
    tareable: false,
    minReading: 0,
    maxReading: 2000,
    maxSeconds: 20
  },

  goLinkForce: {
    appletClass: 'GoIO',
    measurementName: "Force",
    measurementType: 'force',
    // QUANTITY_FORCE
    typeConstantName: 'force',
    sensorName: "GoIO Force Sensor",
    deviceName: "GoIO",
    samplesPerSecond: 20,
    tareable: true,
    minReading: -50,
    maxReading: 50,
    precision: 2,
    maxSeconds: 10
  },

  goLinkPH: {
    appletClass: 'GoIO',
    measurementName: "Acidity",
    measurementType: 'ph',
    // QUANTITY_PH
    typeConstantName: 'ph',
    sensorName: "GoIO pH Sensor",
    deviceName: "GoIO",
    samplesPerSecond: 10,
    tareable: false,
    minReading: 0,
    maxReading: 14,
    maxSeconds: 60
  },

  goLinkCO2: {
    appletClass: 'GoIO',
    measurementName: "CO₂ Concentration",
    measurementType: 'co2',
    // QUANTITY_CO2_GAS
    typeConstantName: 'co2_gas',
    sensorName: "GoIO CO₂ sensor",
    deviceName: "GoIO",
    samplesPerSecond: 1,
    tareable: false,
    minReading: 0,
    maxReading: 5000,
    maxSeconds: 60
  },

  goLinkO2: {
    appletClass: 'GoIO',
    measurementName: "O₂ Concentration",
    measurementType: 'o2',
    // QUANTITY_OXYGEN_GAS
    typeConstantName: 'oxygen_gas',
    sensorName: "GoIO O₂ sensor",
    deviceName: "GoIO",
    samplesPerSecond: 1,
    tareable: false,
    minReading: 0,
    maxReading: 100,
    maxSeconds: 60
  },

  labQuestMotion: {
    appletClass: 'LabQuest',
    measurementName: "Distance",
    measurementType: 'distance',
    // QUANTITY_DISTANCE
    typeConstantName: 'distance',
    sensorName: "LabQuest Motion Sensor",
    deviceName: "LabQuest",
    samplesPerSecond: 20,
    tareable: true,
    minReading: -2,
    maxReading: 2,
    precision: 2,
    maxSeconds: 20
  },

  labQuestTemperature: {
    appletClass: 'LabQuest',
    measurementName: "Temperature",
    measurementType: 'temperature',
    // QUANTITY_TEMPERATURE
    typeConstantName: 'temperature',
    sensorName: "LabQuest Temperature Sensor",
    deviceName: "LabQuest",
    samplesPerSecond: 10,
    tareable: false,
    minReading: 0,
    maxReading: 40,
    maxSeconds: 20
  },

  labQuestLight: {
    appletClass: 'LabQuest',
    measurementName: "Light Intensity",
    measurementType: 'light',
    // QUANTITY_LIGHT
    typeConstantName: 'light',
    sensorName: "LabQuest Light Sensor",
    deviceName: "LabQuest",
    samplesPerSecond: 10,
    tareable: false,
    minReading: 0,
    maxReading: 2000,
    maxSeconds: 20
  },

  labQuestForce: {
    appletClass: 'LabQuest',
    measurementName: "Force",
    measurementType: 'force',
    // QUANTITY_FORCE
    typeConstantName: 'force',
    sensorName: "LabQuest Force Sensor",
    deviceName: "LabQuest",
    samplesPerSecond: 20,
    tareable: true,
    minReading: -50,
    maxReading: 50,
    precision: 2,
    maxSeconds: 10
  },

  labQuestPH: {
    appletClass: 'LabQuest',
    measurementName: "Acidity",
    measurementType: 'ph',
    // QUANTITY_PH
    typeConstantName: 'ph',
    sensorName: "LabQuest pH Sensor",
    deviceName: "LabQuest",
    samplesPerSecond: 10,
    tareable: false,
    minReading: 0,
    maxReading: 14,
    maxSeconds: 60
  },

  labQuestCO2: {
    appletClass: 'LabQuest',
    measurementName: "CO₂ Concentration",
    measurementType: 'co2',
    // QUANTITY_CO2_GAS
    typeConstantName: 'co2_gas',
    sensorName: "LabQuest CO₂ sensor",
    deviceName: "LabQuest",
    samplesPerSecond: 1,
    tareable: false,
    minReading: 0,
    maxReading: 5000,
    maxSeconds: 60
  },

  labQuestO2: {
    appletClass: 'LabQuest',
    measurementName: "O₂ Concentration",
    measurementType: 'o2',
    // QUANTITY_OXYGEN_GAS
    typeConstantName: 'oxygen_gas',
    sensorName: "LabQuest O₂ sensor",
    deviceName: "LabQuest",
    samplesPerSecond: 1,
    tareable: false,
    minReading: 0,
    maxReading: 100,
    maxSeconds: 60
  }
};

},{}],8:[function(require,module,exports){
module.exports = {
  units: {
    time: {
      name: "second",
      pluralName: "seconds",
      symbol: "s"
    },
    distance: {
      name: "meter",
      pluralName: "meters",
      symbol: "m"
    },
    temperature: {
      name: "degree Celsius",
      pluaralName: "degrees Celsius",
      symbol: "°C"
    },
    light: {
      name: "lux",
      pluralName: "lux",
      symbol: "lux"
    },
    force: {
      name: "Newton",
      pluralName: "Newtons",
      symbol: "N"
    },
    ph: {
      name: "pH Unit",
      pluralName: "pH Units",
      symbol: "pH"
    },
    co2: {
      name: "part per million",
      pluralName: "parts per million",
      symbol: "ppm"
    },
    o2: {
      name: "part per million",
      pluralName: "parts per million",
      symbol: "ppm"
    }
  }
};

},{}],9:[function(require,module,exports){
var appletClasses = require('./lib/applet-classes');
var errors = require('./lib/errors');

module.exports = {
  GoIO:                      appletClasses.GoIO,
  LabQuest:                  appletClasses.LabQuest,

  // Listing of supported sensors. You need to set the measurementType on a SensorApplet instance
  // before calling append. The keys of the sensorDefinitions map are the supported
  // measurementType values.
  sensorDefinitions:         require('./lib/sensor-definitions'),
  unitsDefinition:           require('./lib/units-definition'),

  // Error Classes. These are returned to appendCallback or thrown by some of the API methods.
  JavaLoadError:             errors.JavaLoadError,
  AppletInitializationError: errors.AppletInitializationError,
  SensorConnectionError:     errors.SensorConnectionError,
  AlreadyReadingError:       errors.AlreadyReadingError
};

},{"./lib/applet-classes":1,"./lib/errors":2,"./lib/sensor-definitions":7,"./lib/units-definition":8}],10:[function(require,module,exports){


//
// The shims in this file are not fully implemented shims for the ES5
// features, but do work for the particular usecases there is in
// the other modules.
//

var toString = Object.prototype.toString;
var hasOwnProperty = Object.prototype.hasOwnProperty;

// Array.isArray is supported in IE9
function isArray(xs) {
  return toString.call(xs) === '[object Array]';
}
exports.isArray = typeof Array.isArray === 'function' ? Array.isArray : isArray;

// Array.prototype.indexOf is supported in IE9
exports.indexOf = function indexOf(xs, x) {
  if (xs.indexOf) return xs.indexOf(x);
  for (var i = 0; i < xs.length; i++) {
    if (x === xs[i]) return i;
  }
  return -1;
};

// Array.prototype.filter is supported in IE9
exports.filter = function filter(xs, fn) {
  if (xs.filter) return xs.filter(fn);
  var res = [];
  for (var i = 0; i < xs.length; i++) {
    if (fn(xs[i], i, xs)) res.push(xs[i]);
  }
  return res;
};

// Array.prototype.forEach is supported in IE9
exports.forEach = function forEach(xs, fn, self) {
  if (xs.forEach) return xs.forEach(fn, self);
  for (var i = 0; i < xs.length; i++) {
    fn.call(self, xs[i], i, xs);
  }
};

// Array.prototype.map is supported in IE9
exports.map = function map(xs, fn) {
  if (xs.map) return xs.map(fn);
  var out = new Array(xs.length);
  for (var i = 0; i < xs.length; i++) {
    out[i] = fn(xs[i], i, xs);
  }
  return out;
};

// Array.prototype.reduce is supported in IE9
exports.reduce = function reduce(array, callback, opt_initialValue) {
  if (array.reduce) return array.reduce(callback, opt_initialValue);
  var value, isValueSet = false;

  if (2 < arguments.length) {
    value = opt_initialValue;
    isValueSet = true;
  }
  for (var i = 0, l = array.length; l > i; ++i) {
    if (array.hasOwnProperty(i)) {
      if (isValueSet) {
        value = callback(value, array[i], i, array);
      }
      else {
        value = array[i];
        isValueSet = true;
      }
    }
  }

  return value;
};

// String.prototype.substr - negative index don't work in IE8
if ('ab'.substr(-1) !== 'b') {
  exports.substr = function (str, start, length) {
    // did we get a negative start, calculate how much it is from the beginning of the string
    if (start < 0) start = str.length + start;

    // call the original function
    return str.substr(start, length);
  };
} else {
  exports.substr = function (str, start, length) {
    return str.substr(start, length);
  };
}

// String.prototype.trim is supported in IE9
exports.trim = function (str) {
  if (str.trim) return str.trim();
  return str.replace(/^\s+|\s+$/g, '');
};

// Function.prototype.bind is supported in IE9
exports.bind = function () {
  var args = Array.prototype.slice.call(arguments);
  var fn = args.shift();
  if (fn.bind) return fn.bind.apply(fn, args);
  var self = args.shift();
  return function () {
    fn.apply(self, args.concat([Array.prototype.slice.call(arguments)]));
  };
};

// Object.create is supported in IE9
function create(prototype, properties) {
  var object;
  if (prototype === null) {
    object = { '__proto__' : null };
  }
  else {
    if (typeof prototype !== 'object') {
      throw new TypeError(
        'typeof prototype[' + (typeof prototype) + '] != \'object\''
      );
    }
    var Type = function () {};
    Type.prototype = prototype;
    object = new Type();
    object.__proto__ = prototype;
  }
  if (typeof properties !== 'undefined' && Object.defineProperties) {
    Object.defineProperties(object, properties);
  }
  return object;
}
exports.create = typeof Object.create === 'function' ? Object.create : create;

// Object.keys and Object.getOwnPropertyNames is supported in IE9 however
// they do show a description and number property on Error objects
function notObject(object) {
  return ((typeof object != "object" && typeof object != "function") || object === null);
}

function keysShim(object) {
  if (notObject(object)) {
    throw new TypeError("Object.keys called on a non-object");
  }

  var result = [];
  for (var name in object) {
    if (hasOwnProperty.call(object, name)) {
      result.push(name);
    }
  }
  return result;
}

// getOwnPropertyNames is almost the same as Object.keys one key feature
//  is that it returns hidden properties, since that can't be implemented,
//  this feature gets reduced so it just shows the length property on arrays
function propertyShim(object) {
  if (notObject(object)) {
    throw new TypeError("Object.getOwnPropertyNames called on a non-object");
  }

  var result = keysShim(object);
  if (exports.isArray(object) && exports.indexOf(object, 'length') === -1) {
    result.push('length');
  }
  return result;
}

var keys = typeof Object.keys === 'function' ? Object.keys : keysShim;
var getOwnPropertyNames = typeof Object.getOwnPropertyNames === 'function' ?
  Object.getOwnPropertyNames : propertyShim;

if (new Error().hasOwnProperty('description')) {
  var ERROR_PROPERTY_FILTER = function (obj, array) {
    if (toString.call(obj) === '[object Error]') {
      array = exports.filter(array, function (name) {
        return name !== 'description' && name !== 'number' && name !== 'message';
      });
    }
    return array;
  };

  exports.keys = function (object) {
    return ERROR_PROPERTY_FILTER(object, keys(object));
  };
  exports.getOwnPropertyNames = function (object) {
    return ERROR_PROPERTY_FILTER(object, getOwnPropertyNames(object));
  };
} else {
  exports.keys = keys;
  exports.getOwnPropertyNames = getOwnPropertyNames;
}

// Object.getOwnPropertyDescriptor - supported in IE8 but only on dom elements
function valueObject(value, key) {
  return { value: value[key] };
}

if (typeof Object.getOwnPropertyDescriptor === 'function') {
  try {
    Object.getOwnPropertyDescriptor({'a': 1}, 'a');
    exports.getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
  } catch (e) {
    // IE8 dom element issue - use a try catch and default to valueObject
    exports.getOwnPropertyDescriptor = function (value, key) {
      try {
        return Object.getOwnPropertyDescriptor(value, key);
      } catch (e) {
        return valueObject(value, key);
      }
    };
  }
} else {
  exports.getOwnPropertyDescriptor = valueObject;
}

},{}],11:[function(require,module,exports){
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.

var shims = require('_shims');

var formatRegExp = /%[sdj%]/g;
exports.format = function(f) {
  if (!isString(f)) {
    var objects = [];
    for (var i = 0; i < arguments.length; i++) {
      objects.push(inspect(arguments[i]));
    }
    return objects.join(' ');
  }

  var i = 1;
  var args = arguments;
  var len = args.length;
  var str = String(f).replace(formatRegExp, function(x) {
    if (x === '%%') return '%';
    if (i >= len) return x;
    switch (x) {
      case '%s': return String(args[i++]);
      case '%d': return Number(args[i++]);
      case '%j':
        try {
          return JSON.stringify(args[i++]);
        } catch (_) {
          return '[Circular]';
        }
      default:
        return x;
    }
  });
  for (var x = args[i]; i < len; x = args[++i]) {
    if (isNull(x) || !isObject(x)) {
      str += ' ' + x;
    } else {
      str += ' ' + inspect(x);
    }
  }
  return str;
};

/**
 * Echos the value of a value. Trys to print the value out
 * in the best way possible given the different types.
 *
 * @param {Object} obj The object to print out.
 * @param {Object} opts Optional options object that alters the output.
 */
/* legacy: obj, showHidden, depth, colors*/
function inspect(obj, opts) {
  // default options
  var ctx = {
    seen: [],
    stylize: stylizeNoColor
  };
  // legacy...
  if (arguments.length >= 3) ctx.depth = arguments[2];
  if (arguments.length >= 4) ctx.colors = arguments[3];
  if (isBoolean(opts)) {
    // legacy...
    ctx.showHidden = opts;
  } else if (opts) {
    // got an "options" object
    exports._extend(ctx, opts);
  }
  // set default options
  if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
  if (isUndefined(ctx.depth)) ctx.depth = 2;
  if (isUndefined(ctx.colors)) ctx.colors = false;
  if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
  if (ctx.colors) ctx.stylize = stylizeWithColor;
  return formatValue(ctx, obj, ctx.depth);
}
exports.inspect = inspect;


// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
inspect.colors = {
  'bold' : [1, 22],
  'italic' : [3, 23],
  'underline' : [4, 24],
  'inverse' : [7, 27],
  'white' : [37, 39],
  'grey' : [90, 39],
  'black' : [30, 39],
  'blue' : [34, 39],
  'cyan' : [36, 39],
  'green' : [32, 39],
  'magenta' : [35, 39],
  'red' : [31, 39],
  'yellow' : [33, 39]
};

// Don't use 'blue' not visible on cmd.exe
inspect.styles = {
  'special': 'cyan',
  'number': 'yellow',
  'boolean': 'yellow',
  'undefined': 'grey',
  'null': 'bold',
  'string': 'green',
  'date': 'magenta',
  // "name": intentionally not styling
  'regexp': 'red'
};


function stylizeWithColor(str, styleType) {
  var style = inspect.styles[styleType];

  if (style) {
    return '\u001b[' + inspect.colors[style][0] + 'm' + str +
           '\u001b[' + inspect.colors[style][1] + 'm';
  } else {
    return str;
  }
}


function stylizeNoColor(str, styleType) {
  return str;
}


function arrayToHash(array) {
  var hash = {};

  shims.forEach(array, function(val, idx) {
    hash[val] = true;
  });

  return hash;
}


function formatValue(ctx, value, recurseTimes) {
  // Provide a hook for user-specified inspect functions.
  // Check that value is an object with an inspect function on it
  if (ctx.customInspect &&
      value &&
      isFunction(value.inspect) &&
      // Filter out the util module, it's inspect function is special
      value.inspect !== exports.inspect &&
      // Also filter out any prototype objects using the circular check.
      !(value.constructor && value.constructor.prototype === value)) {
    var ret = value.inspect(recurseTimes);
    if (!isString(ret)) {
      ret = formatValue(ctx, ret, recurseTimes);
    }
    return ret;
  }

  // Primitive types cannot have properties
  var primitive = formatPrimitive(ctx, value);
  if (primitive) {
    return primitive;
  }

  // Look up the keys of the object.
  var keys = shims.keys(value);
  var visibleKeys = arrayToHash(keys);

  if (ctx.showHidden) {
    keys = shims.getOwnPropertyNames(value);
  }

  // Some type of object without properties can be shortcutted.
  if (keys.length === 0) {
    if (isFunction(value)) {
      var name = value.name ? ': ' + value.name : '';
      return ctx.stylize('[Function' + name + ']', 'special');
    }
    if (isRegExp(value)) {
      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
    }
    if (isDate(value)) {
      return ctx.stylize(Date.prototype.toString.call(value), 'date');
    }
    if (isError(value)) {
      return formatError(value);
    }
  }

  var base = '', array = false, braces = ['{', '}'];

  // Make Array say that they are Array
  if (isArray(value)) {
    array = true;
    braces = ['[', ']'];
  }

  // Make functions say that they are functions
  if (isFunction(value)) {
    var n = value.name ? ': ' + value.name : '';
    base = ' [Function' + n + ']';
  }

  // Make RegExps say that they are RegExps
  if (isRegExp(value)) {
    base = ' ' + RegExp.prototype.toString.call(value);
  }

  // Make dates with properties first say the date
  if (isDate(value)) {
    base = ' ' + Date.prototype.toUTCString.call(value);
  }

  // Make error with message first say the error
  if (isError(value)) {
    base = ' ' + formatError(value);
  }

  if (keys.length === 0 && (!array || value.length == 0)) {
    return braces[0] + base + braces[1];
  }

  if (recurseTimes < 0) {
    if (isRegExp(value)) {
      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
    } else {
      return ctx.stylize('[Object]', 'special');
    }
  }

  ctx.seen.push(value);

  var output;
  if (array) {
    output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
  } else {
    output = keys.map(function(key) {
      return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
    });
  }

  ctx.seen.pop();

  return reduceToSingleString(output, base, braces);
}


function formatPrimitive(ctx, value) {
  if (isUndefined(value))
    return ctx.stylize('undefined', 'undefined');
  if (isString(value)) {
    var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
                                             .replace(/'/g, "\\'")
                                             .replace(/\\"/g, '"') + '\'';
    return ctx.stylize(simple, 'string');
  }
  if (isNumber(value))
    return ctx.stylize('' + value, 'number');
  if (isBoolean(value))
    return ctx.stylize('' + value, 'boolean');
  // For some reason typeof null is "object", so special case here.
  if (isNull(value))
    return ctx.stylize('null', 'null');
}


function formatError(value) {
  return '[' + Error.prototype.toString.call(value) + ']';
}


function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
  var output = [];
  for (var i = 0, l = value.length; i < l; ++i) {
    if (hasOwnProperty(value, String(i))) {
      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
          String(i), true));
    } else {
      output.push('');
    }
  }

  shims.forEach(keys, function(key) {
    if (!key.match(/^\d+$/)) {
      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
          key, true));
    }
  });
  return output;
}


function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
  var name, str, desc;
  desc = shims.getOwnPropertyDescriptor(value, key) || { value: value[key] };
  if (desc.get) {
    if (desc.set) {
      str = ctx.stylize('[Getter/Setter]', 'special');
    } else {
      str = ctx.stylize('[Getter]', 'special');
    }
  } else {
    if (desc.set) {
      str = ctx.stylize('[Setter]', 'special');
    }
  }

  if (!hasOwnProperty(visibleKeys, key)) {
    name = '[' + key + ']';
  }
  if (!str) {
    if (shims.indexOf(ctx.seen, desc.value) < 0) {
      if (isNull(recurseTimes)) {
        str = formatValue(ctx, desc.value, null);
      } else {
        str = formatValue(ctx, desc.value, recurseTimes - 1);
      }
      if (str.indexOf('\n') > -1) {
        if (array) {
          str = str.split('\n').map(function(line) {
            return '  ' + line;
          }).join('\n').substr(2);
        } else {
          str = '\n' + str.split('\n').map(function(line) {
            return '   ' + line;
          }).join('\n');
        }
      }
    } else {
      str = ctx.stylize('[Circular]', 'special');
    }
  }
  if (isUndefined(name)) {
    if (array && key.match(/^\d+$/)) {
      return str;
    }
    name = JSON.stringify('' + key);
    if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
      name = name.substr(1, name.length - 2);
      name = ctx.stylize(name, 'name');
    } else {
      name = name.replace(/'/g, "\\'")
                 .replace(/\\"/g, '"')
                 .replace(/(^"|"$)/g, "'");
      name = ctx.stylize(name, 'string');
    }
  }

  return name + ': ' + str;
}


function reduceToSingleString(output, base, braces) {
  var numLinesEst = 0;
  var length = shims.reduce(output, function(prev, cur) {
    numLinesEst++;
    if (cur.indexOf('\n') >= 0) numLinesEst++;
    return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
  }, 0);

  if (length > 60) {
    return braces[0] +
           (base === '' ? '' : base + '\n ') +
           ' ' +
           output.join(',\n  ') +
           ' ' +
           braces[1];
  }

  return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
}


// NOTE: These type checking functions intentionally don't use `instanceof`
// because it is fragile and can be easily faked with `Object.create()`.
function isArray(ar) {
  return shims.isArray(ar);
}
exports.isArray = isArray;

function isBoolean(arg) {
  return typeof arg === 'boolean';
}
exports.isBoolean = isBoolean;

function isNull(arg) {
  return arg === null;
}
exports.isNull = isNull;

function isNullOrUndefined(arg) {
  return arg == null;
}
exports.isNullOrUndefined = isNullOrUndefined;

function isNumber(arg) {
  return typeof arg === 'number';
}
exports.isNumber = isNumber;

function isString(arg) {
  return typeof arg === 'string';
}
exports.isString = isString;

function isSymbol(arg) {
  return typeof arg === 'symbol';
}
exports.isSymbol = isSymbol;

function isUndefined(arg) {
  return arg === void 0;
}
exports.isUndefined = isUndefined;

function isRegExp(re) {
  return isObject(re) && objectToString(re) === '[object RegExp]';
}
exports.isRegExp = isRegExp;

function isObject(arg) {
  return typeof arg === 'object' && arg;
}
exports.isObject = isObject;

function isDate(d) {
  return isObject(d) && objectToString(d) === '[object Date]';
}
exports.isDate = isDate;

function isError(e) {
  return isObject(e) && objectToString(e) === '[object Error]';
}
exports.isError = isError;

function isFunction(arg) {
  return typeof arg === 'function';
}
exports.isFunction = isFunction;

function isPrimitive(arg) {
  return arg === null ||
         typeof arg === 'boolean' ||
         typeof arg === 'number' ||
         typeof arg === 'string' ||
         typeof arg === 'symbol' ||  // ES6 symbol
         typeof arg === 'undefined';
}
exports.isPrimitive = isPrimitive;

function isBuffer(arg) {
  return arg && typeof arg === 'object'
    && typeof arg.copy === 'function'
    && typeof arg.fill === 'function'
    && typeof arg.binarySlice === 'function'
  ;
}
exports.isBuffer = isBuffer;

function objectToString(o) {
  return Object.prototype.toString.call(o);
}


function pad(n) {
  return n < 10 ? '0' + n.toString(10) : n.toString(10);
}


var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
              'Oct', 'Nov', 'Dec'];

// 26 Feb 16:19:34
function timestamp() {
  var d = new Date();
  var time = [pad(d.getHours()),
              pad(d.getMinutes()),
              pad(d.getSeconds())].join(':');
  return [d.getDate(), months[d.getMonth()], time].join(' ');
}


// log is just a thin wrapper to console.log that prepends a timestamp
exports.log = function() {
  console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
};


/**
 * Inherit the prototype methods from one constructor into another.
 *
 * The Function.prototype.inherits from lang.js rewritten as a standalone
 * function (not on Function.prototype). NOTE: If this file is to be loaded
 * during bootstrapping this function needs to be rewritten using some native
 * functions as prototype setup using normal JavaScript does not work as
 * expected during bootstrapping (see mirror.js in r114903).
 *
 * @param {function} ctor Constructor function which needs to inherit the
 *     prototype.
 * @param {function} superCtor Constructor function to inherit prototype from.
 */
exports.inherits = function(ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = shims.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
};

exports._extend = function(origin, add) {
  // Don't do anything if add isn't an object
  if (!add || !isObject(add)) return origin;

  var keys = shims.keys(add);
  var i = keys.length;
  while (i--) {
    origin[keys[i]] = add[keys[i]];
  }
  return origin;
};

function hasOwnProperty(obj, prop) {
  return Object.prototype.hasOwnProperty.call(obj, prop);
}

},{"_shims":10}]},{},[9])
(9)
});
;

define('models/sensor-common/i18n-sensor-definitions',['require','sensor-applet'],function(require) {
  var sensorDefinitions = require('sensor-applet').sensorDefinitions;

  return function getTranslatedSensorDefinitions(i18n) {
    sensorDefinitions.goMotion.measurementName = i18n.t("sensor.measurements.distance");
    sensorDefinitions.goMotion.sensorName = i18n.t("sensor.names.goMotion");

    sensorDefinitions.goTemp.measurementName = i18n.t("sensor.measurements.temperature");
    sensorDefinitions.goTemp.sensorName = i18n.t("sensor.names.goTemp");

    sensorDefinitions.goLinkTemperature.measurementName = i18n.t("sensor.measurements.temperature");
    sensorDefinitions.goLinkTemperature.sensorName = i18n.t("sensor.names.goLinkTemperature");

    sensorDefinitions.goLinkLight.measurementName = i18n.t("sensor.measurements.light_intensity");
    sensorDefinitions.goLinkLight.sensorName = i18n.t("sensor.names.goLinkLight");

    sensorDefinitions.goLinkForce.measurementName = i18n.t("sensor.measurements.force");
    sensorDefinitions.goLinkForce.sensorName = i18n.t("sensor.names.goLinkForce");

    sensorDefinitions.goLinkPH.measurementName = i18n.t("sensor.measurements.acidity");
    sensorDefinitions.goLinkPH.sensorName = i18n.t("sensor.names.goLinkPH");

    sensorDefinitions.goLinkCO2.measurementName = i18n.t("sensor.measurements.CO2_concentration");
    sensorDefinitions.goLinkCO2.sensorName = i18n.t("sensor.names.goLinkCO2");

    sensorDefinitions.goLinkO2.measurementName = i18n.t("sensor.measurements.O2_concentration");
    sensorDefinitions.goLinkO2.sensorName = i18n.t("sensor.names.goLinkO2");

    sensorDefinitions.labQuestMotion.measurementName = i18n.t("sensor.measurements.distance");
    sensorDefinitions.labQuestMotion.sensorName = i18n.t("sensor.names.labQuestMotion");

    sensorDefinitions.labQuestTemperature.measurementName = i18n.t("sensor.measurements.temperature");
    sensorDefinitions.labQuestTemperature.sensorName = i18n.t("sensor.names.labQuestTemperature");

    sensorDefinitions.labQuestLight.measurementName = i18n.t("sensor.measurements.light_intensity");
    sensorDefinitions.labQuestLight.sensorName = i18n.t("sensor.names.labQuestLight");

    sensorDefinitions.labQuestForce.measurementName = i18n.t("sensor.measurements.force");
    sensorDefinitions.labQuestForce.sensorName = i18n.t("sensor.names.labQuestForce");

    sensorDefinitions.labQuestPH.measurementName = i18n.t("sensor.measurements.acidity");
    sensorDefinitions.labQuestPH.sensorName = i18n.t("sensor.names.labQuestPH");

    sensorDefinitions.labQuestCO2.measurementName = i18n.t("sensor.measurements.CO2_concentration");
    sensorDefinitions.labQuestCO2.sensorName = i18n.t("sensor.names.labQuestCO2");

    sensorDefinitions.labQuestO2.measurementName = i18n.t("sensor.measurements.O2_concentration");
    sensorDefinitions.labQuestO2.sensorName = i18n.t("sensor.names.labQuestO2");

    return sensorDefinitions;
  };
});

/*global define */
define('models/sensor-common/notifier',['require','common/controllers/basic-dialog'],function (require) {
  var BasicDialog    = require('common/controllers/basic-dialog');

  function Notifier(i18n){
    this._i18n = i18n;
  }

  Notifier.prototype.alert = function(message, buttons, options) {
    var opts = $.extend({width: "60%", buttons: buttons, closeOnEscape: false}, options);
    var dialog = new BasicDialog(opts, this._i18n);

    dialog.setContent("<div>" + message + "</div>");
    dialog.open();
  };

  Notifier.prototype.status = function(message, options) {
    var opts = $.extend({width: "60%", closeOnEscape: false, modal: true}, options);
    var dialog = new BasicDialog(opts, this._i18n);

    dialog.setContent("<div>" + message + "</div>");
    dialog.open();
    return dialog;
  };

  return Notifier;
});

/*global define: false, $: true */

define('models/sensor/modeler',['require','lab.config','common/lab-modeler-mixin','common/property-description','./metadata','sensor-applet','models/sensor-common/i18n-sensor-definitions','models/sensor-common/notifier','common/controllers/export-controller'],function(require) {

  var labConfig            = require('lab.config'),
      LabModelerMixin      = require('common/lab-modeler-mixin'),
      PropertyDescription  = require('common/property-description'),
      metadata             = require('./metadata'),
      sensorApplet         = require('sensor-applet'),
      unitsDefinition      = sensorApplet.unitsDefinition,
      getSensorDefinitions = require('models/sensor-common/i18n-sensor-definitions'),
      Notifier             = require('models/sensor-common/notifier'),
      ExportController     = require('common/controllers/export-controller');

  return function Model(initialProperties, opt) {
    var i18n = opt.i18n,
        notifier = new Notifier(i18n),

        labModelerMixin,
        propertySupport,
        dispatch,
        isStopped = true,
        needsReload = false,
        initialSensorType,
        sensorType,
        applet,
        isSensorReady = false,
        isSensorInitializing = false,
        sensorPollsPerSecond = 1,
        sensorPollingIntervalID,
        samplesPerSecond,
        time,
        rawSensorValue,
        stepCounter,
        didCollectData,
        isTaring,
        isSensorTareable,
        initialTareValue,
        model;

    var defaultSensorReadingDescriptionHash = {
      label: i18n.t("sensor.measurements.sensor_reading"),
      unitAbbreviation: "-",
      format: '.2f',
      min: 0,
      max: 1
    };
    var sensorDefinitions = getSensorDefinitions(i18n);

    function updatePropertyRange(property, min, max) {
      var descriptionHash;
      var description;

      descriptionHash = model.getPropertyDescription(property).getHash();
      descriptionHash.min = min;
      descriptionHash.max = max;

      description = new PropertyDescription(unitsDefinition, descriptionHash);
      propertySupport.setPropertyDescription(property, description);
    }

    // Updates min, max of displayTime to be [0..collectionTime]
    function updateDisplayTimeRange() {
      if (model.properties.collectionTime == null) {
        return;
      }
      updatePropertyRange('displayTime', 0, model.properties.collectionTime);
    }

    function removeApplet() {
      if (applet) {
        applet.removeListeners('data');
        applet.removeListeners('deviceUnplugged');
        applet.removeListeners('sensorUnplugged');

        applet.remove();
      }

      model.makeInvalidatingChange(function() {
        isSensorReady = false;
        isSensorInitializing = false;
      });
    }

    function appendApplet() {
      model.makeInvalidatingChange(function() {
        isSensorInitializing = true;
      });

      // Wrapped in a setTimeout to allow all property observers to finish their work before
      // actually firing up the applet, which freezes the UI and possibly blocks until the user
      // clicks through security dialogs to allow the applet to run.
      // TODO: A setImmediate shim (using window.postMessage) would be useful here.
      setTimeout(function() {
        applet.on('data', appletDataCallback);
        applet.on('deviceUnplugged', function() { handleUnplugged('device'); });
        applet.on('sensorUnplugged', function() { handleUnplugged('sensor'); });

        applet.append($('body'),function(error) {

          if (error) {
            if (error instanceof sensorApplet.JavaLoadError) {
              handleLoadingFailure(i18n.t("sensor.messages.java_applet_error"));
            } else if (error instanceof sensorApplet.AppletInitializationError) {
              handleLoadingFailure(i18n.t("sensor.messages.java_applet_not_loading"));
            } else if (error instanceof sensorApplet.SensorConnectionError) {
              handleSensorConnectionError();
            } else {
              handleLoadingFailure(i18n.t("sensor.messages.unexpected_error"));
            }
            model.makeInvalidatingChange(function() {
              isSensorInitializing = false;
            });
            return;
          }

          model.makeInvalidatingChange(function() {
            isSensorReady = true;
            isSensorInitializing = false;
          });
        });
      }, 10);
    }

    function handleSensorConnectionError() {
      removeApplet();
      var buttons = {};
      buttons[i18n.t("sensor.messages.try_again")] = function() {
        $(this).dialog("close");
        // This is a workaround: currently, the applet itself does not appear to respond to its
        // initialization methods if the sensor was not connected when the applet started up.
        appendApplet();
      };
      buttons[i18n.t("sensor.messages.cancel")] = function() {
        $(this).remove();
        model.reload();
      };
      notifier.alert(i18n.t("sensor.messages.sensor_not_attached", {sensor_name: model.properties.sensorName}), buttons);
    }

    function handleLoadingFailure(message) {
      removeApplet();
      notifier.alert(message, {
        OK: function() {
          $(this).remove();
          model.reload();
        }
      });
    }

    function handleUnplugged(what) {
      removeApplet();
      model.stop();
      ExportController.logAction("Unplugged" + model.properties[what+'Name']);
      var buttons = {};
      buttons[i18n.t("sensor.messages.try_again")] = function() {
        $(this).dialog("close");
        appendApplet();
      };
      buttons[i18n.t("sensor.messages.cancel")] = function() {
        $(this).dialog("close");
      };
      notifier.alert(i18n.t("sensor.messages.sensor_or_device_unplugged", {sensor_or_device_name: model.properties[what+'Name']}), buttons);
    }

    function startPollingSensor() {
      if (sensorPollingIntervalID) {
        clearInterval(sensorPollingIntervalID);
      }
      var handleSensorValues = function(error, values) {
        if (error) {
          if (error instanceof sensorApplet.AlreadyReadingError) {
            // Don't worry about it -- we just overlapped another call to readSensor
          } else if(error instanceof sensorApplet.SensorConnectionError){
            clearInterval(sensorPollingIntervalID);
            handleSensorConnectionError();
          } else {
            clearInterval(sensorPollingIntervalID);
            throw error;
          }
        } else {
          model.makeInvalidatingChange(function() {
            rawSensorValue = values[0];
            if (isTaring) {
              model.properties.tareValue = rawSensorValue;
              isTaring = false;
            }
          });
        }
      };
      sensorPollingIntervalID = setInterval(function() {
        applet.readSensor(handleSensorValues);
      }, 1000/sensorPollsPerSecond);
    }

    function stopPollingSensor() {
      if (sensorPollingIntervalID) {
        clearInterval(sensorPollingIntervalID);
        sensorPollingIntervalID = null;
      }
    }

    function initializeStateVariables() {
      stepCounter = 0;
      time = 0;
      rawSensorValue = undefined;
      didCollectData = false;
      isSensorTareable = false;
      isTaring = false;
    }

    function setSensorType(_sensorType) {
      var AppletClass;
      var sensorDefinition;
      var description;
      var measurementType;

      if (sensorType === _sensorType) {
        return;
      }

      if (sensorType) {
        // drop the tare value if we're changing from one sensor type to another!
        model.properties.tareValue = 0;
      }

      sensorType = _sensorType;
      rawSensorValue = undefined;

      if (applet) {
        removeApplet();
      }

      if (sensorType) {
        sensorDefinition = sensorDefinitions[sensorType];
        samplesPerSecond = sensorDefinition.samplesPerSecond;
        measurementType = sensorDefinition.measurementType;
        AppletClass = sensorApplet[sensorDefinition.appletClass];
        isSensorTareable = sensorDefinition.tareable;

        applet = window.Lab.sensor[sensorType] = new AppletClass({
          listenerPath: 'Lab.sensor.' + sensorType,
          sensorDefinitions: [sensorDefinition],
          appletId: sensorType+'-sensor',
          codebase: labConfig.rootUrl + "/jars/lab-sensor-applet-interface-dist"
        });

        appendApplet();

        // Update the description of the main 'sensorReading' output
        description = new PropertyDescription(unitsDefinition, {
          label: sensorDefinition.measurementName,
          unitType: measurementType,
          min: sensorDefinition.minReading,
          max: sensorDefinition.maxReading
        });

        propertySupport.setPropertyDescription('sensorReading', description);

        // Override collectionTime  only if it wasn't set on the model definition
        if (model.properties.collectionTime == null) {
          model.properties.collectionTime = sensorDefinition.maxSeconds;
        }
      } else if (model.properties.hasOwnProperty('sensorReading')) {
        // no sensor type
        description = new PropertyDescription(unitsDefinition, defaultSensorReadingDescriptionHash);
        propertySupport.setPropertyDescription('sensorReading', description);
      }
    }

    function appletDataCallback(d) {
      stepCounter++;

      time += (1 / samplesPerSecond);

      // Whaaa? Accessing 'window' seems to prevent a strange bug in which Safari 6.0 stops updating
      // time after 3.7s. Hard to debug because accessing console, window, or Web Inspector makes
      // the problem go away!
      window.__bizarreSafariFix = 1;

      rawSensorValue = d[0];
      // Once we collect data for a given sensor, don't allow changingn the sensor typea
      if (!didCollectData) {
        model.freeze('sensorType');
      }

      didCollectData = true;
      model.updateAllOutputProperties();
      dispatch.tick();
    }

    model = {

      on: function(type, listener) {
        dispatch.on(type, listener);
      },

      start: function() {
        if (!model.properties.isPlayable) {
          return;
        }

        applet.start(function(error, isStarted) {
          if (error) {
            if (error instanceof sensorApplet.SensorConnectionError) {
              handleSensorConnectionError();
            }
          } else if (isStarted) {
            model.makeInvalidatingChange(function() {
              isStopped = false;
            });
            dispatch.play();
          }
        });
      },

      stop: function() {

        if (applet) {
          applet.stop();
        }
        model.makeInvalidatingChange(function() {
          isStopped = true;
        });
        dispatch.stop();
      },

      tare: function() {
        if (!isStopped) {
          throw new Error("Sensor model: tare() called on a non-stopped model.");
        }
        if (sensorPollingIntervalID != null && rawSensorValue != null) {
          model.properties.tareValue = rawSensorValue;
        } else {
          model.makeInvalidatingChange(function() {
            isTaring = true;
          });
        }
      },

      willReset: function() {
        dispatch.willReset();
      },

      reset: function() {
        model.stop();
        removeApplet();

        initializeStateVariables();
        model.properties.tareValue = initialTareValue;
        model.unfreeze('sensorType');
        model.properties.sensorType = initialSensorType;

        dispatch.reset();
      },

      reload: function() {
        model.stop();
        model.makeInvalidatingChange(function() {
          needsReload = true;
        });
      },

      isStopped: function() {
        return isStopped;
      },

      stepCounter: function() {
        return stepCounter;
      },

      serialize: function () { return ""; }
    };

    initializeStateVariables();

    // Need to define a globally-accessible 'listenerPath' for the sensor to evaluate
    if (window.Lab === undefined) {
      window.Lab = {};
    }
    window.Lab.sensor = {};

    labModelerMixin = new LabModelerMixin({
      metadata: metadata,
      setters: {
        sensorType: setSensorType
      },
      unitsDefinition: unitsDefinition,
      initialProperties: initialProperties,
      usePlaybackSupport: false
    });

    labModelerMixin.mixInto(model);
    propertySupport = labModelerMixin.propertySupport;
    dispatch = labModelerMixin.dispatchSupport;
    dispatch.addEventTypes("tick", "play", "stop", "tickStart", "tickEnd");

    // Remember thse values so that the model can be reset properly
    initialSensorType = model.properties.sensorType;
    initialTareValue = model.properties.tareValue;

    model.defineOutput('time', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.2f'
    }, function() {
      return time;
    });

    model.defineOutput('displayTime', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.2f'
    }, function() {
      return time;
    });

    model.defineOutput('sensorReading', defaultSensorReadingDescriptionHash, function() {
      if (rawSensorValue == null) {
        return rawSensorValue;
      }
      return rawSensorValue - model.properties.tareValue;
    });

    // TODO. Need a better way for the model to be able to have a property which it can set the
    // value of at arbitrary times, but which is read-only to client code. Outputs aren't quite
    // the right solution because the invalidation stuff is really about time and physics-based
    // invalidation.

    model.defineOutput('sensorName', {
      label: "Sensor Name"
    }, function() {
      return sensorDefinitions[sensorType].sensorName;
    });

    model.defineOutput('deviceName', {
      label: "Sensor Interface Device Name"
    }, function() {
      return sensorDefinitions[sensorType].deviceName;
    });

    model.defineOutput('isStopped', {
      label: "Stopped?"
    }, function() {
      return isStopped;
    });

    // TODO. We need a way to make "model-writable" read only properties.
    model.defineOutput('isPlayable', {
      label: "Startable?"
    }, function() {
      return isSensorReady && !didCollectData;
    });

    model.defineOutput('hasPlayed', {
      label: "Has successfully collected data?"
    }, function() {
      return didCollectData;
    });

    model.defineOutput('shouldPollSensor', {
      label: "Polling Sensor?"
    }, function() {
      return model.properties.isPlayable && isStopped;
    });

    model.defineOutput('isSensorInitializing', {
      label: "Loading Sensor?"
    }, function() {
      return isSensorInitializing;
    });

    model.defineOutput('isTaring', {
      label: "Waiting for a tare value?"
    }, function() {
      return isTaring;
    });

    model.defineOutput('canTare', {
      label: "Can set a tare value?"
    }, function() {
      return isStopped && !didCollectData && isSensorTareable && !isTaring;
    });

    model.defineOutput('needsReload', {
      label: "Needs Reload?"
    }, function() {
      return needsReload;
    });

    // Clean up state before we go -- failing to remove the applet from the page before switching
    // between 2 sensor types that use the same interface causes an applet exception.
    model.on('willReset.model', removeApplet);

    model.addObserver('shouldPollSensor', function() {
      if (model.properties.shouldPollSensor) {
        startPollingSensor();
      } else {
        stopPollingSensor();
      }
    });

    model.addObserver('collectionTime', updateDisplayTimeRange);
    updateDisplayTimeRange();

    // Kick things off by doing this explicitly:
    setSensorType(model.properties.sensorType);

    return model;
  };
});

define('models/sensor/view',['common/views/select-box-view','common/views/numeric-output-view','sensor-applet','common/views/view-state'],function() {

  var SelectBoxView = require('common/views/select-box-view'),
      NumericOutputView = require('common/views/numeric-output-view'),
      sensorDefinitions = require('sensor-applet').sensorDefinitions,
      viewState = require('common/views/view-state');

  return function(model, modelUrl, i18n) {

    var sensorTypeView = new SelectBoxView({
      id: 'sensor-type-view',
      options: [{
        value: null,
        text: i18n.t("sensor.select_sensor_type"),
        selected: model.properties.sensorType == null,
        disabled: true
      }].concat(Object.keys(sensorDefinitions).map(function(key) {
        return {
          value: key,
          text: sensorDefinitions[key].sensorName,
          selected: key === model.properties.sensorType,
          disabled: false
        };
      })),
      onChange: function(option) {
        model.properties.sensorType = option.value;
      }
    });

    // TODO use the formatter from the property description. Right now, it automatically adds
    // units to the returned string (which we don't want here).
    var format = d3.format('.2f');
    var sensorReadingView;
    var view;

    function setIsTaringState() {
      if (model.properties.isTaring) {
        view.$zeroButton.find('button').html(i18n.t("sensor.zeroing"));
      } else {
        view.$zeroButton.find('button').html(i18n.t("sensor.zero"));
      }
    }

    function setCanTareState() {
      if (model.properties.canTare) {
        viewState.enableView(view.$zeroButton);
      } else {
        viewState.disableView(view.$zeroButton);
      }
    }

    function setSensorTypeDisabledState() {
      var description = model.getPropertyDescription('sensorType');
      if (description.getFrozen()) {
        viewState.disableView(view.$selectBox);
      } else {
        viewState.enableView(view.$selectBox);
      }
    }

    function setupModelObservers() {
      model.addObserver('isTaring', setIsTaringState);
      setIsTaringState();

      model.addObserver('canTare', setCanTareState);
      setCanTareState();

      model.addPropertyDescriptionObserver('sensorType', setSensorTypeDisabledState);
      setSensorTypeDisabledState();
    }

    return view = {
      $el: $("<div id='model-container' class='container sensor-model-container' />"),

      bindModel: function(newModel, newModelUrl) {
        modelUrl = newModelUrl || modelUrl;
        model = newModel || model;

        setupModelObservers();
      },

      getHeightForWidth: function() {
        return "2.6em";
      },

      // called once we're in the DOM
      setup: function() {

        this.$el.empty();

        sensorReadingView = new NumericOutputView({
          id: 'sensor-value-view',
          label: i18n.t("sensor.reading"),
          units: model.getPropertyDescription('sensorReading').getUnitAbbreviation()
        });

        var $selectBox = sensorTypeView.render(this.$el),
            $zeroButton = $("<div><button>" + i18n.t("sensor.zero") + "</button></div>"),
            $sensorReading = sensorReadingView.render();

        $selectBox.addClass('interactive-pulldown component component-spacing');
        $zeroButton.addClass('interactive-button component component-spacing');
        $sensorReading.addClass('numeric-output component horizontal component-spacing');

        this.$el.css('zIndex', 4);
        this.$el.append($selectBox);
        this.$el.append($sensorReading);
        this.$el.append($zeroButton);

        this.$zeroButton = $zeroButton;
        this.$selectBox = $selectBox;

        sensorReadingView.resize();
        setupModelObservers();

        $zeroButton.on('click', 'button', function() {
          model.tare();
        });
      },

      showInitializationProgress: function() {
        var $progressbar;

        if (!this.$progressbarContainer) {
          $progressbar = $('<div/>')
            .attr('id', 'sensor-progressbar');

          this.$progressbarContainer = $('<div/>')
            .attr('id', 'sensor-progressbar-container')
            .css('bottom', $('body').height() / 2 + 75 + $('.lab-responsive-content').offset().top)
            .append('<div class="label">' + i18n.t('sensor.loading_sensor') + '</div>')
            .append($progressbar)
            .appendTo('.lab-responsive-content');

          $progressbar.progressbar({ value: false });
        }

        this.$progressbarContainer.show();
      },

      hideInitializationProgress: function() {
        this.$progressbarContainer.hide();
      },

      resize: function() {
        if (sensorReadingView) {
          sensorReadingView.resize();
        }
      },

      repaint: function() {},

      setFocus: function () {},

      updateUnits: function(units) {
        sensorReadingView.updateUnits(units);
        if (model.properties.sensorReading == null) {
          sensorReadingView.hideUnits();
        }
      },

      update: function() {
        if (model.properties.sensorReading == null) {
          sensorReadingView.update("");
          sensorReadingView.hideUnits();
        } else {
          sensorReadingView.update(format(model.properties.sensorReading));
          sensorReadingView.showUnits();
        }
      }
    };
  };
});

define('models/sensor/scripting-api',[],function () {
  /**
    Define the model-specific Sensor scripting API used by 'action' scripts on interactive elements.

    The universal Interactive scripting API is extended with the properties of the
    object below which will be exposed to the interactive's 'action' scripts as if
    they were local vars. All other names (including all globals, but excluding
    Javascript builtins) will be unavailable in the script context; and scripts
    are run in strict mode so they don't accidentally expose or read globals.

    @param: parent Common Scripting API
  */
  return function SensorScriptingAPI (parent) {
    return {
      /**
       * Reset the model. The interactives controller will emit a 'willResetModel'.
       * The willResetModel observers can ask to wait for asynchronous confirmation before
       * the model is actually reset.
       * Note that the effect would be almost the same like after reload operation. However
       * the mechanism under the hood is completely different. The biggest difference for
       * authoring is that when .resetModel() is called, the "onLoad" script won't be
       * executed again. It may be also faster than reload.
       * @param  {object} options hash of options, supported properties:
       *                         * propertiesToRetain - a list of properties to save before
       *                           the model reset and restore after reset.
       *                         * cause - cause of the reset action.
       */
      resetModel: function resetModel(options) {
        parent.intController.resetModel(options);
      }
    };
  };
});

/*global define */

define('models/sensor/controller',['require','common/controllers/model-controller','./modeler','./view','./scripting-api'],function (require) {
  // Dependencies.
  var ModelController   = require('common/controllers/model-controller'),
      Model             = require('./modeler'),
      ModelContainer    = require('./view'),
      ScriptingAPI      = require('./scripting-api');

  return function (modelUrl, modelOptions, interactiveController) {
    var controller = new ModelController(modelUrl, modelOptions, interactiveController,
      Model, ModelContainer, ScriptingAPI);

    // Note to self: modelController doesn't emit modelLoaded when the model first loads.
    // This was unexpected...

    function setupModelObservers() {
      var model = controller.model;

      model.addObserver('isSensorInitializing', function() {
        var view = controller.modelContainer;

        if (model.properties.isSensorInitializing) {
          view.showInitializationProgress();
        } else {
          view.hideInitializationProgress();
        }
      });

      model.addObserver('sensorReading', function() {
        // if the model is running, the tick handler will take care of it
        if (model.isStopped()) {
          controller.updateView();
        }
      });

      model.addObserver('needsReload', function() {
        if (model.properties.needsReload) {
          interactiveController.reloadModel();
        }
      });

      model.addPropertyDescriptionObserver('sensorReading', function() {
        var description = model.getPropertyDescription('sensorReading');
        var view = controller.modelContainer;

        view.updateUnits(description.getUnitAbbreviation());
      });
    }

    interactiveController.on('modelLoaded.sensor-model-controller', setupModelObservers);

    interactiveController.on('modelReset.sensor-model-controller', function() {
      controller.model.set('isNewRunInProgress', false);
    });

    interactiveController.on('willResetModel', function() {
      controller.model.set('isNewRunInProgress', true);
    });

    return controller;
  };
});

/*global define: false */

define('models/dual-sensor/metadata',[],function() {

  return {
    mainProperties: {
      type: {
        defaultValue: "dual-sensor",
        immutable: true
      },
      sensorType: {
        defaultValue: null
      },
      sensorType2: {
        defaultValue: null
      },
      samplesPerSecond: {
        readOnly: true
      },
      collectionTime: {
        defaultValue: null
      },
      tareValue: {
        defaultValue: 0
      },
      tareValue2: {
        defaultValue: 0
      },
      clientId: {
        defaultValue: null
      },
      useRandomClientId: {
        defaultValue: false
      }
    },
    viewOptions: {
      showClock: {
        defaultValue: true,
        propertyChangeInvalidates: false
      },
      controlButtons: {
        defaultValue: "play_reset",
        propertyChangeInvalidates: false
      },
      controlButtonStyle: {
        defaultValue: "video",
        propertyChangeInvalidates: false,
        serialize: false
      }
    }
  };
});

/*global define: false, d3: false $: true */

define('models/dual-sensor/modeler',['require','common/property-support','common/property-description','cs!common/filters/running-average-filter','common/validator','./metadata','sensor-applet','models/sensor-common/i18n-sensor-definitions','lab.config','models/sensor-common/notifier','common/controllers/export-controller'],function(require) {

  var PropertySupport      = require('common/property-support'),
      PropertyDescription  = require('common/property-description'),
      RunningAverageFilter = require('cs!common/filters/running-average-filter'),
      validator            = require('common/validator'),
      metadata             = require('./metadata'),
      sensorApplet         = require('sensor-applet'),
      unitsDefinition      = sensorApplet.unitsDefinition,
      getSensorDefinitions = require('models/sensor-common/i18n-sensor-definitions'),
      labConfig            = require('lab.config'),
      Notifier             = require('models/sensor-common/notifier'),
      ExportController     = require('common/controllers/export-controller');

  return function Model(initialProperties, opt) {
    var propertySupport = new PropertySupport({
          types: ['mainProperty', 'viewOption', 'parameter', 'output']
        }),

        i18n = opt.i18n,
        notifier = new Notifier(i18n),

        viewOptions,
        mainProperties,
        isStopped = true,
        needsReload = false,
        dispatch = d3.dispatch('play', 'stop', 'tick',  'tickStart', 'willReset', 'reset', 'stepForward',
                               'stepBack', 'seek', 'invalidation'),
        initialSensorType,
        sensorType,
        sensorType2,
        applet,
        isSensorReady = false,
        isSensorInitializing = false,
        sensorPollsPerSecond = 1,
        sensorPollingIntervalID,
        samplesPerSecond = 10,
        time,
        rawSensorValue,
        rawSensorValue2,
        stepCounter,
        didCollectData,
        isTaring,
        isTaring2,
        isSensorTareable,
        isSensorTareable2,
        initialTareValue,
        invalidatingChangeNestingLevel = 0,
        filteredOutputs = [],
        customSetters,
        model;

    var defaultSensorReadingDescriptionHash = {
      label: i18n.t("sensor.measurements.sensor_reading"),
      unitAbbreviation: "-",
      format: '.2f',
      min: 0,
      max: 1
    };
    var sensorDefinitions = getSensorDefinitions(i18n);

    //
    // The following function is essentially copied from MD2D modeler, and should moved to a common
    // module
    //
    function defineBuiltinProperty(key, type, setter) {
      var metadataForType,
          descriptor,
          propertyChangeInvalidates,
          unitType;

      if (type === 'mainProperty') {
        metadataForType = metadata.mainProperties;
      } else if (type === 'viewOption') {
        metadataForType = metadata.viewOptions;
      } else {
        throw new Error(type + " is not a supported built-in property type");
      }

      propertyChangeInvalidates = validator.propertyChangeInvalidates(metadataForType[key]);

      descriptor = {
        type: type,
        writable: validator.propertyIsWritable(metadataForType[key]),
        set: setter,
        includeInHistoryState: !!metadataForType[key].storeInTickHistory,
        validate: function(value) {
          return validator.validateSingleProperty(metadataForType[key], key, value, false);
        },
        beforeSetCallback: propertyChangeInvalidates ? invalidatingChangePreHook : undefined,
        afterSetCallback: propertyChangeInvalidates ? invalidatingChangePostHook : undefined
      };

      unitType = metadataForType[key].unitType;
      if (unitType) {
        descriptor.description = new PropertyDescription(unitsDefinition, { unitType: unitType });
      } else {
        // A property with no units should at least have a label
        descriptor.description = new PropertyDescription(null, {
          label: metadataForType.label || key
        });
      }

      propertySupport.defineProperty(key, descriptor);
    }

    function invalidatingChangePreHook() {
      if (invalidatingChangeNestingLevel === 0) {
        propertySupport.storeComputedProperties();
        propertySupport.deleteComputedPropertyCachedValues();
        propertySupport.enableCaching = false;
      }
      invalidatingChangeNestingLevel++;
    }

    function invalidatingChangePostHook() {
      invalidatingChangeNestingLevel--;
      updateFilteredOutputs();
      if (invalidatingChangeNestingLevel === 0) {
        propertySupport.enableCaching = true;
        propertySupport.notifyChangedComputedProperties();
      }
    }

    function makeInvalidatingChange(closure) {
      invalidatingChangePreHook();
      closure();
      invalidatingChangePostHook();
    }

    function updateFilteredOutputs() {
      filteredOutputs.forEach(function(output) {
        output.addSample();
      });
    }

    function updatePropertyRange(property, min, max) {
      var descriptionHash;
      var description;

      descriptionHash = model.getPropertyDescription(property).getHash();
      descriptionHash.min = min;
      descriptionHash.max = max;

      description = new PropertyDescription(unitsDefinition, descriptionHash);
      propertySupport.setPropertyDescription(property, description);
    }

    // Updates min, max of displayTime to be [0..collectionTime]
    function updateDisplayTimeRange() {
      if (model.properties.collectionTime == null) {
        return;
      }
      updatePropertyRange('displayTime', 0, model.properties.collectionTime);
    }

    function removeApplet() {
      if (applet) {
        applet.removeListeners('data');
        applet.removeListeners('deviceUnplugged');
        applet.removeListeners('sensorUnplugged');

        applet.remove();
      }

      makeInvalidatingChange(function() {
        isSensorReady = false;
        isSensorInitializing = false;
      });
    }

    function appendApplet() {
      makeInvalidatingChange(function() {
        isSensorInitializing = true;
      });

      // Wrapped in a setTimeout to allow all property observers to finish their work before
      // actually firing up the applet, which freezes the UI and possibly blocks until the user
      // clicks through security dialogs to allow the applet to run.
      // TODO: A setImmediate shim (using window.postMessage) would be useful here.
      setTimeout(function() {
        applet.on('data', appletDataCallback);
        applet.on('deviceUnplugged', function() { handleUnplugged('device'); });
        applet.on('sensorUnplugged', function() { handleUnplugged('sensor'); });

        applet.append($('body'),function(error) {

          if (error) {
            if (error instanceof sensorApplet.JavaLoadError) {
              handleLoadingFailure(i18n.t("sensor.messages.java_applet_error"));
            } else if (error instanceof sensorApplet.AppletInitializationError) {
              handleLoadingFailure(i18n.t("sensor.messages.java_applet_not_loading"));
            } else if (error instanceof sensorApplet.SensorConnectionError) {
              handleSensorConnectionError();
            } else {
              handleLoadingFailure(i18n.t("sensor.messages.unexpected_error"));
            }
            makeInvalidatingChange(function() {
              isSensorInitializing = false;
            });
            return;
          }

          makeInvalidatingChange(function() {
            isSensorReady = true;
            isSensorInitializing = false;
          });
        });
      }, 10);
    }

    function handleSensorConnectionError() {
      removeApplet();
      var buttons = {};
      buttons[i18n.t("sensor.messages.try_again")] = function() {
        $(this).dialog("close");
        // This is a workaround: currently, the applet itself does not appear to respond to its
        // initialization methods if the sensor was not connected when the applet started up.
        appendApplet();
      };
      buttons[i18n.t("sensor.messages.cancel")] = function() {
        $(this).remove();
        model.reload();
      };
      notifier.alert(i18n.t("sensor.messages.sensor_not_attached", {sensor_name: model.properties.sensorName}), buttons);
    }

    function handleLoadingFailure(message) {
      removeApplet();
      notifier.alert(message, {
        OK: function() {
          $(this).remove();
          model.reload();
        }
      });
    }

    function handleUnplugged(what) {
      removeApplet();
      model.stop();
      ExportController.logAction("Unplugged" + model.properties[what+'Name']);
      var buttons = {};
      buttons[i18n.t("sensor.messages.try_again")] = function() {
        $(this).dialog("close");
        appendApplet();
      };
      buttons[i18n.t("sensor.messages.cancel")] = function() {
        $(this).dialog("close");
      };
      notifier.alert(i18n.t("sensor.messages.sensor_or_device_unplugged", {sensor_or_device_name: model.properties[what+'Name']}), buttons);
    }

    function startPollingSensor() {
      if (sensorPollingIntervalID) {
        clearInterval(sensorPollingIntervalID);
      }
      var handleSensorValues = function(error, values) {
        if (error) {
          if (error instanceof sensorApplet.AlreadyReadingError) {
            // Don't worry about it -- we just overlapped another call to readSensor
          } else if(error instanceof sensorApplet.SensorConnectionError){
            clearInterval(sensorPollingIntervalID);
            handleSensorConnectionError();
          } else {
            clearInterval(sensorPollingIntervalID);
            throw error;
          }
        } else {
          makeInvalidatingChange(function() {
            rawSensorValue = values[0];
            rawSensorValue2 = values[1];
            if (isTaring) {
              model.properties.tareValue = rawSensorValue;
              isTaring = false;
            }
            if (isTaring2) {
              model.properties.tareValue2 = rawSensorValue2;
              isTaring2 = false;
            }
          });
        }
      };
      sensorPollingIntervalID = setInterval(function() {
        applet.readSensor(handleSensorValues);
      }, 1000/sensorPollsPerSecond);
    }

    function stopPollingSensor() {
      if (sensorPollingIntervalID) {
        clearInterval(sensorPollingIntervalID);
        sensorPollingIntervalID = null;
      }
    }

    function initializeStateVariables() {
      stepCounter = 0;
      time = 0;
      rawSensorValue = undefined;
      rawSensorValue2 = undefined;
      didCollectData = false;
      isSensorTareable = false;
      isSensorTareable2 = false;
      isTaring = false;
      isTaring2 = false;
    }

    function setupApplet() {
      if (sensorType && sensorType2) {
        var sensorDefinition  = sensorDefinitions[sensorType],
            sensorDefinition2 = sensorDefinitions[sensorType2],
            AppletClass = sensorApplet[sensorDefinition.appletClass];

        applet = window.Lab.sensor[sensorType+sensorType2] = new AppletClass({
          listenerPath: 'Lab.sensor.' + sensorType + sensorType2,
          sensorDefinitions: [sensorDefinition, sensorDefinition2],
          appletId: sensorType+sensorType2+'-sensor',
          codebase: labConfig.rootUrl + "/jars/lab-sensor-applet-interface-dist"
        });

        appendApplet();
      }

    }

    function updateSamplesPerSecond() {
      if (sensorType && sensorType2) {
        var sensorDefinition  = sensorDefinitions[sensorType],
            sensorDefinition2 = sensorDefinitions[sensorType2];

        samplesPerSecond = Math.max(sensorDefinition.samplesPerSecond, sensorDefinition2.samplesPerSecond);
      }
    }

    function setSensorType(_sensorType) {
      var sensorDefinition;
      var description;
      var measurementType;

      if (sensorType === _sensorType) {
        return;
      }

      if (sensorType) {
        // drop the tare value if we're changing from one sensor type to another!
        model.properties.tareValue = 0;
      }

      sensorType = _sensorType;
      rawSensorValue = undefined;

      if (applet) {
        removeApplet();
      }

      if (sensorType) {
        sensorDefinition = sensorDefinitions[sensorType];
        measurementType = sensorDefinition.measurementType;
        isSensorTareable = sensorDefinition.tareable;
        updateSamplesPerSecond();

        setupApplet();

        // Update the description of the main 'sensorReading' output
        description = new PropertyDescription(unitsDefinition, {
          label: sensorDefinition.measurementName,
          unitType: measurementType,
          min: sensorDefinition.minReading,
          max: sensorDefinition.maxReading
        });

        propertySupport.setPropertyDescription('sensorReading', description);

        // Override collectionTime  only if it wasn't set on the model definition
        if (model.properties.collectionTime == null) {
          model.properties.collectionTime = sensorDefinition.maxSeconds;
        }
      } else if (model.properties.hasOwnProperty('sensorReading')) {
        // no sensor type
        description = new PropertyDescription(unitsDefinition, defaultSensorReadingDescriptionHash);
        propertySupport.setPropertyDescription('sensorReading', description);
      }
    }

    function setSensorType2(_sensorType) {
      var sensorDefinition;
      var description;
      var measurementType;

      if (sensorType2 === _sensorType) {
        return;
      }

      if (sensorType2) {
        // drop the tare value if we're changing from one sensor type to another!
        model.properties.tareValue2 = 0;
      }

      sensorType2 = _sensorType;
      rawSensorValue2 = undefined;

      if (applet) {
        removeApplet();
      }

      if (sensorType2) {
        sensorDefinition = sensorDefinitions[sensorType2];
        measurementType = sensorDefinition.measurementType;
        isSensorTareable2 = sensorDefinition.tareable;
        updateSamplesPerSecond();

        setupApplet();

        // Update the description of the main 'sensorReading' output
        description = new PropertyDescription(unitsDefinition, {
          label: sensorDefinition.measurementName,
          unitType: measurementType,
          min: sensorDefinition.minReading,
          max: sensorDefinition.maxReading
        });

        propertySupport.setPropertyDescription('sensorReading2', description);

        // Override collectionTime  only if it wasn't set on the model definition
        if (model.properties.collectionTime == null) {
          model.properties.collectionTime = sensorDefinition.maxSeconds;
        }
      } else if (model.properties.hasOwnProperty('sensorReading2')) {
        // no sensor type
        description = new PropertyDescription(unitsDefinition, defaultSensorReadingDescriptionHash);
        propertySupport.setPropertyDescription('sensorReading2', description);
      }
    }

    function appletDataCallback(d) {
      stepCounter++;

      time += (1 / samplesPerSecond);

      // Whaaa? Accessing 'window' seems to prevent a strange bug in which Safari 6.0 stops updating
      // time after 3.7s. Hard to debug because accessing console, window, or Web Inspector makes
      // the problem go away!
      window.__bizarreSafariFix = 1;

      rawSensorValue = d[0];
      rawSensorValue2 = d[1];
      // Once we collect data for a given sensor, don't allow changingn the sensor typea
      if (!didCollectData) {
        model.freeze('sensorType');
        model.freeze('sensorType2');
      }

      didCollectData = true;

      propertySupport.deleteComputedPropertyCachedValues();
      propertySupport.notifyAllComputedProperties();
      updateFilteredOutputs();

      dispatch.tick();
    }

    model = {

      on: function(type, listener) {
        dispatch.on(type, listener);
      },

      start: function() {
        if (!model.properties.isPlayable) {
          return;
        }

        applet.start(function(error, isStarted) {
          if (error) {
            if (error instanceof sensorApplet.SensorConnectionError) {
              handleSensorConnectionError();
            }
          } else if (isStarted) {
            makeInvalidatingChange(function() {
              isStopped = false;
            });
            dispatch.play();
          }
        });
      },

      stop: function() {

        if (applet) {
          applet.stop();
        }
        makeInvalidatingChange(function() {
          isStopped = true;
        });
        dispatch.stop();
      },

      tare: function() {
        if (!isStopped) {
          throw new Error("Sensor model: tare() called on a non-stopped model.");
        }
        if (sensorPollingIntervalID != null && rawSensorValue != null) {
          model.properties.tareValue = rawSensorValue;
        } else {
          makeInvalidatingChange(function() {
            isTaring = true;
          });
        }
      },

      tare2: function() {
        if (!isStopped) {
          throw new Error("Sensor model: tare() called on a non-stopped model.");
        }
        if (sensorPollingIntervalID != null && rawSensorValue2 != null) {
          model.properties.tareValue2 = rawSensorValue2;
        } else {
          makeInvalidatingChange(function() {
            isTaring2 = true;
          });
        }
      },

      willReset: function() {
        dispatch.willReset();
      },

      reset: function() {
        model.stop();
        removeApplet();

        initializeStateVariables();
        model.unfreeze('sensorType');
        model.unfreeze('sensorType2');
        model.properties.sensorType = initialSensorType;
        model.properties.sensorType2 = initialSensorType;

        dispatch.reset();
      },

      reload: function() {
        model.stop();
        makeInvalidatingChange(function() {
          needsReload = true;
        });
      },

      isStopped: function() {
        return isStopped;
      },

      stepCounter: function() {
        return stepCounter;
      },

      //
      // The following are essentially copied from MD2D modeler, and should moved to a common module
      //

      addPropertiesListener: function(properties, callback) {
        if (typeof properties === 'string') {
          model.addObserver(properties, callback);
        } else {
          properties.forEach(function(property) {
            model.addObserver(property, callback);
          });
        }
      },

      defineParameter: function(key, descriptionHash, setter) {
        var descriptor = {
              type: 'parameter',
              includeInHistoryState: true,
              invokeSetterAfterBulkRestore: false,
              description: new PropertyDescription(unitsDefinition, descriptionHash),
              beforeSetCallback: invalidatingChangePreHook,
              afterSetCallback: invalidatingChangePostHook
            };

        // In practice, some parameters are meant only to be observed, and have no setter
        if (setter) {
          descriptor.set = function(value) {
            setter.call(model, value);
          };
        }
        propertySupport.defineProperty(key, descriptor);
      },

      defineOutput: function(key, descriptionHash, getter) {
        propertySupport.defineProperty(key, {
          type: 'output',
          writable: false,
          get: getter,
          includeInHistoryState: false,
          description: new PropertyDescription(unitsDefinition, descriptionHash)
        });
      },

      defineFilteredOutput: function(key, description, filteredPropertyKey, type, period) {
        var filter, initialValue;

        if (type === "RunningAverage") {
          filter = new RunningAverageFilter(period);
        } else {
          throw new Error("FilteredOutput: unknown filter type " + type + ".");
        }

        // Add initial sample
        initialValue = model.properties[key];
        if (initialValue === undefined || isNaN(Number(initialValue))) {
          throw new Error("FilteredOutput: property is not a valid numeric value or it is undefined.");
        }
        filter.addSample(model.properties.time, initialValue);

        filteredOutputs.push({
          addSample: function() {
            filter.addSample(model.properties.time, model.properties[filteredPropertyKey]);
          }
        });

        // Extend description to contain information about filter
        description.property = filteredPropertyKey;
        description.type = type;
        description.period = period;

        model.defineOutput(key, description, function () {
          return filter.calculate();
        });
      },

      serialize: function () { return ""; }
    };

    initializeStateVariables();

    // Need to define a globally-accessible 'listenerPath' for the sensor to evaluate
    if (window.Lab === undefined) {
      window.Lab = {};
    }
    window.Lab.sensor = {};

    propertySupport.mixInto(model);

    customSetters = {
      sensorType: setSensorType,
      sensorType2: setSensorType2
    };

    mainProperties = validator.validateCompleteness(metadata.mainProperties, initialProperties);
    Object.keys(mainProperties).forEach(function(key) {
      defineBuiltinProperty(key, 'mainProperty', customSetters[key]);
    });
    propertySupport.setRawValues(mainProperties);

    // Remember thse values so that the model can be reset properly
    initialSensorType = model.properties.sensorType;
    initialTareValue = model.properties.tareValue;

    viewOptions = validator.validateCompleteness(metadata.viewOptions, initialProperties.viewOptions || {});
    Object.keys(viewOptions).forEach(function(key) {
      defineBuiltinProperty(key, 'viewOption');
    });
    propertySupport.setRawValues(viewOptions);

    model.defineOutput('time', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.2f'
    }, function() {
      return time;
    });

    model.defineOutput('displayTime', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.2f'
    }, function() {
      return time;
    });

    model.defineOutput('sensorReading', defaultSensorReadingDescriptionHash, function() {
      if (rawSensorValue == null) {
        return rawSensorValue;
      }
      return rawSensorValue - model.properties.tareValue;
    });

    model.defineOutput('sensorReading2', defaultSensorReadingDescriptionHash, function() {
      if (rawSensorValue2 == null) {
        return rawSensorValue2;
      }
      return rawSensorValue2 - model.properties.tareValue2;
    });

    // TODO. Need a better way for the model to be able to have a property which it can set the
    // value of at arbitrary times, but which is read-only to client code. Outputs aren't quite
    // the right solution because the invalidation stuff is really about time and physics-based
    // invalidation.

    model.defineOutput('sensorName', {
      label: "Sensor Name"
    }, function() {
      return sensorDefinitions[sensorType].sensorName;
    });

    model.defineOutput('sensorName2', {
      label: "Sensor Name"
    }, function() {
      return sensorDefinitions[sensorType2].sensorName;
    });

    model.defineOutput('deviceName', {
      label: "Sensor Interface Device Name"
    }, function() {
      return sensorDefinitions[sensorType].deviceName;
    });

    model.defineOutput('deviceName2', {
      label: "Sensor Interface Device Name"
    }, function() {
      return sensorDefinitions[sensorType2].deviceName;
    });

    model.defineOutput('isStopped', {
      label: "Stopped?"
    }, function() {
      return isStopped;
    });

    // TODO. We need a way to make "model-writable" read only properties.
    model.defineOutput('isPlayable', {
      label: "Startable?"
    }, function() {
      return isSensorReady && !didCollectData;
    });

    model.defineOutput('hasPlayed', {
      label: "Has successfully collected data?"
    }, function() {
      return didCollectData;
    });

    model.defineOutput('shouldPollSensor', {
      label: "Polling Sensor?"
    }, function() {
      return model.properties.isPlayable && isStopped;
    });

    model.defineOutput('isSensorInitializing', {
      label: "Loading Sensor?"
    }, function() {
      return isSensorInitializing;
    });

    model.defineOutput('isTaring', {
      label: "Waiting for a tare value?"
    }, function() {
      return isTaring;
    });

    model.defineOutput('isTaring2', {
      label: "Waiting for a tare value?"
    }, function() {
      return isTaring2;
    });

    model.defineOutput('canTare', {
      label: "Can set a tare value?"
    }, function() {
      return isStopped && !didCollectData && isSensorTareable && !isTaring;
    });

    model.defineOutput('canTare2', {
      label: "Can set a tare value?"
    }, function() {
      return isStopped && !didCollectData && isSensorTareable2 && !isTaring2;
    });

    model.defineOutput('needsReload', {
      label: "Needs Reload?"
    }, function() {
      return needsReload;
    });

    // Clean up state before we go -- failing to remove the applet from the page before switching
    // between 2 sensor types that use the same interface causes an applet exception.
    model.on('willReset.model', removeApplet);

    model.addObserver('shouldPollSensor', function() {
      if (model.properties.shouldPollSensor) {
        startPollingSensor();
      } else {
        stopPollingSensor();
      }
    });

    model.addObserver('collectionTime', updateDisplayTimeRange);
    updateDisplayTimeRange();

    // Kick things off by doing this explicitly:
    setSensorType(model.properties.sensorType);
    setSensorType2(model.properties.sensorType2);

    return model;
  };
});

define('models/dual-sensor/view',['common/views/select-box-view','common/views/numeric-output-view','sensor-applet','common/views/view-state'],function() {

  var SelectBoxView = require('common/views/select-box-view'),
      NumericOutputView = require('common/views/numeric-output-view'),
      sensorDefinitions = require('sensor-applet').sensorDefinitions,
      viewState = require('common/views/view-state');

  return function(model, modelUrl, i18n) {

    var sensorTypeView = new SelectBoxView({
      id: 'sensor-type-view',
      options: [{
        value: null,
        text: i18n.t("sensor.select_sensor_type"),
        selected: model.properties.sensorType == null,
        disabled: true
      }].concat(Object.keys(sensorDefinitions).filter(function(key) {
        return sensorDefinitions[key].appletClass === "LabQuest";
      }).map(function(key) {
        return {
          value: key,
          text: sensorDefinitions[key].sensorName,
          selected: key === model.properties.sensorType,
          disabled: false
        };
      })),
      onChange: function(option) {
        model.properties.sensorType = option.value;
      }
    });

    var sensorType2View = new SelectBoxView({
      id: 'sensor-type2-view',
      options: [{
        value: null,
        text: i18n.t("sensor.select_sensor_type"),
        selected: model.properties.sensorType2 == null,
        disabled: true
      }].concat(Object.keys(sensorDefinitions).filter(function(key) {
        return sensorDefinitions[key].appletClass === "LabQuest";
      }).map(function(key) {
        return {
          value: key,
          text: sensorDefinitions[key].sensorName,
          selected: key === model.properties.sensorType2,
          disabled: false
        };
      })),
      onChange: function(option) {
        model.properties.sensorType2 = option.value;
      }
    });

    // TODO use the formatter from the property description. Right now, it automatically adds
    // units to the returned string (which we don't want here).
    var format = d3.format('.2f');
    var sensorReadingView, sensorReading2View;
    var view;

    function setIsTaringState() {
      if (model.properties.isTaring) {
        view.$zeroButton.find('button').html(i18n.t("sensor.zeroing"));
      } else {
        view.$zeroButton.find('button').html(i18n.t("sensor.zero"));
      }
    }

    function setCanTareState() {
      if (model.properties.canTare) {
        viewState.enableView(view.$zeroButton);
      } else {
        viewState.disableView(view.$zeroButton);
      }
    }

    function setIsTaringState2() {
      if (model.properties.isTaring2) {
        view.$zeroButton2.find('button').html(i18n.t("sensor.zeroing"));
      } else {
        view.$zeroButton2.find('button').html(i18n.t("sensor.zero"));
      }
    }

    function setCanTareState2() {
      if (model.properties.canTare2) {
        viewState.enableView(view.$zeroButton2);
      } else {
        viewState.disableView(view.$zeroButton2);
      }
    }

    function setSensorTypeDisabledState() {
      var description = model.getPropertyDescription('sensorType');
      if (description.getFrozen()) {
        viewState.disableView(view.$selectBox);
      } else {
        viewState.enableView(view.$selectBox);
      }
    }

    function setSensorType2DisabledState() {
      var description = model.getPropertyDescription('sensorType2');
      if (description.getFrozen()) {
        viewState.disableView(view.$selectBox2);
      } else {
        viewState.enableView(view.$selectBox2);
      }
    }

    function setupModelObservers() {
      model.addObserver('isTaring', setIsTaringState);
      model.addObserver('isTaring2', setIsTaringState2);
      setIsTaringState();
      setIsTaringState2();

      model.addObserver('canTare', setCanTareState);
      model.addObserver('canTare2', setCanTareState2);
      setCanTareState();
      setCanTareState2();

      model.addPropertyDescriptionObserver('sensorType', setSensorTypeDisabledState);
      setSensorTypeDisabledState();
      model.addPropertyDescriptionObserver('sensorType2', setSensorType2DisabledState);
      setSensorType2DisabledState();
    }

    return view = {
      $el: $("<div id='model-container' class='container sensor-model-container' />"),

      bindModel: function(newModel, newModelUrl) {
        modelUrl = newModelUrl || modelUrl;
        model = newModel || model;

        setupModelObservers();
      },

      getHeightForWidth: function() {
        return "4.8em";
      },

      // called once we're in the DOM
      setup: function() {

        this.$el.empty();

        sensorReadingView = new NumericOutputView({
          id: 'sensor-value-view',
          label: i18n.t("sensor.reading"),
          units: model.getPropertyDescription('sensorReading').getUnitAbbreviation()
        });

        sensorReading2View = new NumericOutputView({
          id: 'sensor-value-2-view',
          label: i18n.t("sensor.reading"),
          units: model.getPropertyDescription('sensorReading2').getUnitAbbreviation()
        });

        var $row1 = $("<div></div>"),
            $row2 = $("<div></div>"),
            $selectBox  = sensorTypeView.render(this.$el),
            $selectBox2 = sensorType2View.render(this.$el),
            $zeroButton = $("<div><button>" + i18n.t("sensor.zero") + "</button></div>"),
            $zeroButton2 = $("<div><button>" + i18n.t("sensor.zero") + "</button></div>"),
            $sensorReading  = sensorReadingView.render(),
            $sensorReading2 = sensorReading2View.render();

        $selectBox.addClass('interactive-pulldown component component-spacing');
        $selectBox2.addClass('interactive-pulldown component component-spacing');
        $zeroButton.addClass('interactive-button component component-spacing');
        $zeroButton2.addClass('interactive-button component component-spacing');
        $sensorReading.addClass('numeric-output component horizontal component-spacing');
        $sensorReading2.addClass('numeric-output component horizontal component-spacing');

        this.$el.css('zIndex', 4);
        this.$el.append($row1);
        this.$el.append($row2);
        $row1.append($selectBox);
        $row1.append($sensorReading);
        $row1.append($zeroButton);
        $row2.append($selectBox2);
        $row2.append($sensorReading2);
        $row2.append($zeroButton2);

        this.$zeroButton = $zeroButton;
        this.$zeroButton2 = $zeroButton2;
        this.$selectBox = $selectBox;
        this.$selectBox2 = $selectBox2;

        sensorReadingView.resize();
        sensorReading2View.resize();

        setupModelObservers();

        $zeroButton.on('click', 'button', function() {
          model.tare();
        });
        $zeroButton2.on('click', 'button', function() {
          model.tare2();
        });
      },

      showInitializationProgress: function() {
        var $progressbar;

        if (!this.$progressbarContainer) {
          $progressbar = $('<div/>')
            .attr('id', 'sensor-progressbar');

          this.$progressbarContainer = $('<div/>')
            .attr('id', 'sensor-progressbar-container')
            .css('bottom', $('body').height() / 2 + 75 + $('.lab-responsive-content').offset().top)
            .append('<div class="label">' + i18n.t('sensor.loading_sensor') + '</div>')
            .append($progressbar)
            .appendTo('.lab-responsive-content');

          $progressbar.progressbar({ value: false });
        }

        this.$progressbarContainer.show();
      },

      hideInitializationProgress: function() {
        this.$progressbarContainer.hide();
      },

      resize: function() {
        if (sensorReadingView) {
          sensorReadingView.resize();
        }

        if (sensorReading2View) {
          sensorReading2View.resize();
        }
      },

      repaint: function() {},

      setFocus: function () {},

      updateUnits: function(units) {
        sensorReadingView.updateUnits(units);
        if (model.properties.sensorReading == null) {
          sensorReadingView.hideUnits();
        }
      },

      updateUnits2: function(units) {
        sensorReading2View.updateUnits(units);
        if (model.properties.sensorReading2 == null) {
          sensorReading2View.hideUnits();
        }
      },

      update: function() {
        if (model.properties.sensorReading == null) {
          sensorReadingView.update("");
          sensorReadingView.hideUnits();
        } else {
          sensorReadingView.update(format(model.properties.sensorReading));
          sensorReadingView.showUnits();
        }

        if (model.properties.sensorReading2 == null) {
          sensorReading2View.update("");
          sensorReading2View.hideUnits();
        } else {
          sensorReading2View.update(format(model.properties.sensorReading2));
          sensorReading2View.showUnits();
        }
      }
    };
  };
});

define('models/dual-sensor/scripting-api',[],function () {
  /**
    Define the model-specific Dual Sensor scripting API used by 'action' scripts on interactive elements.

    The universal Interactive scripting API is extended with the properties of the
    object below which will be exposed to the interactive's 'action' scripts as if
    they were local vars. All other names (including all globals, but excluding
    Javascript builtins) will be unavailable in the script context; and scripts
    are run in strict mode so they don't accidentally expose or read globals.

    @param: parent Common Scripting API
  */
  return function DualSensorScriptingAPI (parent) {
    return {
      /**
       * Reset the model. The interactives controller will emit a 'willResetModel'.
       * The willResetModel observers can ask to wait for asynchronous confirmation before
       * the model is actually reset.
       * Note that the effect would be almost the same like after reload operation. However
       * the mechanism under the hood is completely different. The biggest difference for
       * authoring is that when .resetModel() is called, the "onLoad" script won't be
       * executed again. It may be also faster than reload.
       * @param  {object} options hash of options, supported properties:
       *                         * propertiesToRetain - a list of properties to save before
       *                           the model reset and restore after reset.
       *                         * cause - cause of the reset action.
       */
      resetModel: function resetModel(options) {
        parent.intController.resetModel(options);
      }
    };
  };
});

/*global define */

define('models/dual-sensor/controller',['require','common/controllers/model-controller','models/dual-sensor/modeler','models/dual-sensor/view','models/dual-sensor/scripting-api'],function (require) {
  // Dependencies.
  var ModelController   = require('common/controllers/model-controller'),
      Model             = require('models/dual-sensor/modeler'),
      ModelContainer    = require('models/dual-sensor/view'),
      ScriptingAPI      = require('models/dual-sensor/scripting-api');

  return function (modelUrl, modelOptions, interactiveController) {
    var controller = new ModelController(modelUrl, modelOptions, interactiveController,
      Model, ModelContainer, ScriptingAPI);

    // Note to self: modelController doesn't emit modelLoaded when the model first loads.
    // This was unexpected...

    function setupModelObservers() {
      var model = controller.model;

      model.addObserver('isSensorInitializing', function() {
        var view = controller.modelContainer;

        if (model.properties.isSensorInitializing) {
          view.showInitializationProgress();
        } else {
          view.hideInitializationProgress();
        }
      });

      model.addObserver('sensorReading', function() {
        // if the model is running, the tick handler will take care of it
        if (model.isStopped()) {
          controller.updateView();
        }
      });

      model.addObserver('sensorReading2', function() {
        // if the model is running, the tick handler will take care of it
        if (model.isStopped()) {
          controller.updateView();
        }
      });

      model.addObserver('needsReload', function() {
        if (model.properties.needsReload) {
          interactiveController.reloadModel();
        }
      });

      // TODO This will have to handle have 2 different units...
      model.addPropertyDescriptionObserver('sensorReading', function() {
        var description = model.getPropertyDescription('sensorReading');
        var view = controller.modelContainer;

        view.updateUnits(description.getUnitAbbreviation());
      });

      model.addPropertyDescriptionObserver('sensorReading2', function() {
        var description = model.getPropertyDescription('sensorReading2');
        var view = controller.modelContainer;

        view.updateUnits2(description.getUnitAbbreviation());
      });
    }

    interactiveController.on('modelLoaded.dual-sensor-model-controller', setupModelObservers);

    interactiveController.on('modelReset.dual-sensor-model-controller', function() {
      controller.model.set('isNewRunInProgress', false);
    });

    interactiveController.on('willResetModel', function() {
      controller.model.set('isNewRunInProgress', true);
    });

    return controller;
  };
});

/*global define: false */

define('models/dual-sensor-connector/metadata',[],function() {

  return {
    mainProperties: {
      type: {
        defaultValue: "dual-sensor-connector",
        immutable: true
      },
      tareValue: {
        defaultValue: 0
      },
      tareValue2: {
        defaultValue: 0
      },
      clientId: {
        defaultValue: null
      },
      clientName: {
        defaultValue: null
      },
      useRandomClientId: {
        defaultValue: false
      },
      useDuration: {
        defaultValue: 'codap',
        storeInTickHistory: false,
        validate: function(value) {
          if (value === true || value === false || value === 'codap') {
            return value;
          }
          throw new Error("Invalid 'useDuration' value: " + value);
        },
      },
      requestedDuration: {
        defaultValue: 10,
        storeInTickHistory: false
      }
    },
    viewOptions: {
      showClock: {
        defaultValue: true,
        propertyChangeInvalidates: false
      },
      controlButtons: {
        defaultValue: "play_reset",
        propertyChangeInvalidates: false
      },
      controlButtonStyle: {
        defaultValue: "video",
        propertyChangeInvalidates: false,
        serialize: false
      }
    }
  };
});

/*global define: false */

define('common/state-machine',[],function() {

  // Dead-simple, non-hierarchical state machine to which you dispatch events. The state machine is
  // in one state at a time (once the initial state is specified) and each event you ask it to
  // dispatch is simply handled by the current state's handler for that event (i.e.., the state's
  // method having the same name of the event). If the current state defines no handler by that
  // name, the event is ignored. Each handler can transition the state machine to a different state
  // by invoking 'this.gotoState(<name of new state>)' in which case the old state's 'leaveState'
  // method will be called, the new state will become the current state, and the new state's
  // 'enterState' method will be called. A state's enterState method can also call this.gotoState
  // (for example, after checking some condition) in which case, the "new, new" state will become
  // the current state. However, remember that this implementation is simple: any code in the
  // enterState method after gotoState will run with the "new, new" state as the current state --
  // not the same state in which the enterState method is defined. Additionally, if you go to a
  // state from itself, the enterState method will simply run again.

  // You configure the state machine by passing its constructor an object having each state as the
  // child objects. Each state's name is taken from the its key in that parent object, and the
  // constructor simply endows each state with a 'name' property and a 'gotoState' method. Each
  // state object should define a method for each event it will handle. Events passed to
  // stateMachine.handleEvent which do not correspond to a method in the current state ware simply
  // ignored. (handleEvent returns true if the state has a handler for the event, false otherwise.)

  // Usage:
  // var stateMachine = new StateMachine({
  //   state1: {
  //     enterState: function() {
  //       console.log("entered state 1");
  //     },
  //     leaveState: function() {
  //       console.log("leaving state 1");
  //     },
  //     switchState: function() {
  //       this.gotoState('state2');
  //     }
  //   },

  //   state2: {
  //     enterState: function() {
  //       console.log("entered state 2");
  //     }
  //   }
  // });

  // > stateMachine.gotoState('state1');
  // entered state 1
  // undefined
  // > stateMachine.handleEvent('switchState');
  // leaving state 1
  // entered state 2
  // false


  function StateMachine(states) {
    var self = this;

    this.states = states;
    this.currentState = null;

    Object.keys(states).forEach(function(key) {
      var state = states[key];
      // for debugging purposes
      if (state.name !== undefined && state.name !== key) {
        throw new Error("State " + key + " already has a 'name' property. The 'name' property is reserved.");
      }
      state.name = key;

      if (state.gotoState !== undefined) {
        throw new Error("State " + key + " already has a 'gotoState' property. The 'gotoState' property is reserved.");
      }
      state.gotoState = function(toStateName) {
        if (this !== self.currentState) {
          throw new Error("gotoState was called on state " + this.name + " but the current state is ", self.getCurrentStateName());
        }
        // just call the prototype method
        self.gotoState(toStateName);
      };

    });
  }


  StateMachine.prototype.getCurrentStateName = function() {
    if (this.currentState) {
      return this.currentState.name;
    } else {
      return "<no state>";
    }
  };


  StateMachine.prototype.gotoState = function(toStateName) {
    var toState = this.states[toStateName];
    if ( ! toState ) {
      throw new Error("gotoState was called with state name " + toStateName + " but no such state exists.");
    }

    if (this.currentState && typeof this.currentState.leaveState === 'function') {
      this.currentState.leaveState();
    }

    this.currentState = toState;

    if (typeof this.currentState.enterState === 'function') {
      this.currentState.enterState();
    }
  };


  StateMachine.prototype.handleEvent = function(eventName /*, eventArgs... */) {
    var eventArgs = Array.prototype.slice.call(arguments, 1);

    if (typeof this.currentState[eventName] === 'function') {
      this.currentState[eventName].apply(this.currentState, eventArgs);
      return true;
    }
    return false;
  };


  return StateMachine;

});


!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define('sensor-connector-interface',e):"undefined"!=typeof window?window.sensorConnectorInterface=e():"undefined"!=typeof global?global.sensorConnectorInterface=e():"undefined"!=typeof self&&(self.sensorConnectorInterface=e())}(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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.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(require,module,exports){
/*globals XDomainRequest */

'use strict';

// datasets[]
//   columns[]
//     id
//     units
//     data[]
//     liveValue
//     requestedValuesTimeStamp
//     receivedValuesTimeStamp

var RSVP = require('rsvp');
var _ = require('lodash');
var Machina = require('machina');

var EventEmitter2 = require('eventemitter2').EventEmitter2;
var events = new EventEmitter2({
  wildcard: true
});

var SensorConnectorState = Machina.Fsm.extend({
  urlQueryParams: '',
  urlPrefix: '',
  datasets: null,

  _TIME_LIMIT_IN_MS: 5000,
  _LAUNCH_TIME_LIMIT_IN_MS: 20000,
  _POLLING_DELAY: 500,
  _COLLECTING_DELAY: 100,

  _timer: null,
  _hostCycler: null,
  _rawQueryParams: {},
  _statusIntervalId: 0,
  _lastStatusTimeStamp: 0,
  _datasetsById: {},
  _columnsById: {},
  _sessionChangedEmitted: false,
  _currentSessionID: undefined,
  _launchFrame: null,

  initialize: function() {
    var fsm = this;
    this._timer = {
      timerId: 0,
      start: function(timeLimit) {
        this._timerId = setTimeout(function() { fsm.handle('timeout'); }, timeLimit);
      },

      reset: function() {
        this.stop();
        this.start();
      },

      stop: function() {
        clearTimeout(this._timerId);
      }
    };

    this._hostCycler = {
      get moreHosts() {
        return fsm._currentHostIdx + 1 < fsm._availableHosts.length;
      },

      nextHost: function() {
        fsm._currentHostIdx++;
        fsm.urlPrefix = fsm._availableHosts[fsm._currentHostIdx];
      },

      reset: function() {
        fsm._currentHostIdx = 0;
        fsm.urlPrefix = fsm._availableHosts[fsm._currentHostIdx];
      }
    };

    this._initializeSession();
  },

  initialState: 'disconnected',
  states: {
    disconnected: {
      _onEnter: function() {
        this._currentSessionID = undefined;
        if (this._launchFrame !== null) {
          document.body.removeChild(this._launchFrame);
          this._launchFrame = null;
        }
      }
    },
    connecting: {
      _onEnter: function() {
        this._hostCycler.reset();
        this._requestStatus();
        this._timer.start(this._TIME_LIMIT_IN_MS);
      },
      statusReceived: function() {
        this._timer.stop();
        this.transition('polling');
      },
      statusErrored: function() {
        if (this._hostCycler.moreHosts) {
          this._hostCycler.nextHost();
          this._requestStatus();
        } else {
          this._hostCycler.reset();
          this._timer.stop();
          this.transition('launching');
        }
      },
      timeout: function() {
        this.transition('launching');
      }
    },
    launching: {
      _onEnter: function() {
        this._injectCcscFrame();
        this._timer.start(this._LAUNCH_TIME_LIMIT_IN_MS);
        this._requestStatus();
      },
      statusReceived: function() {
        this._timer.stop();
        this.transition('polling');
      },
      statusErrored: function() {
        // keep polling until the launch timeout time limit passes, but poll all of the available hosts
        if (this._hostCycler.moreHosts) {
          this._hostCycler.nextHost();
          this._requestStatus();
        } else {
          this._hostCycler.reset();
          setTimeout(function(){
            this._requestStatus();
          }.bind(this), this._POLLING_DELAY);
        }
      },
      timeout: function() {
        this.transition('launchTimedOut');
      }
    },
    polling: {
      _onEnter: function() {
        this._requestStatus();
      },
      _onExit: function() {
        this._timer.stop();
      },
      statusReceived: function(response) {
        this._timer.stop();
        this._processStatus(response);

        if (response.collection.isCollecting) {
          this.transition('collecting');
          return;
        }

        if (! response.collection.canControl) {
          this.transition('controlDisabled');
          return;
        }

        var currentlyAttached = typeof(response.currentInterface) === "undefined" || response.currentInterface === null || response.currentInterface !== "None Found";
        if (!currentlyAttached) {
          this.transition('interfaceMissing');
          return;
        }

        // Schedule the next poll request
        setTimeout(function() { this._requestStatus(); }.bind(this), this._POLLING_DELAY);
        this._timer.start(this._TIME_LIMIT_IN_MS);
      },
      statusErrored: function() {
        // TODO
        this._timer.stop();
        this.transition('errored');
      },
      timeout: function() {
        // TODO
        this.transition('errored');
      }

    },
    collecting: {
      _onEnter: function() {
        events.emit('collectionStarted');
        this._requestStatus();
        this._timer.start(this._TIME_LIMIT_IN_MS);
      },
      _onExit: function() {
        events.emit('collectionStopped');
      },
      statusReceived: function(response) {
        this._timer.stop();

        this._processStatus(response);

        if (! response.collection.isCollecting) {
          this.transition('polling');
          return;
        }

        if (! response.collection.canControl) {
          // Somehow we lost control while collecting. This _shouldn't_ ever happen...
          this.transition('controlDisabled');
          return;
        }

        var currentlyAttached = typeof(response.currentInterface) === "undefined" || response.currentInterface === null || response.currentInterface !== "None Found";
        if (!currentlyAttached) {
          this.transition('interfaceMissing');
          return;
        }

        // Schedule the next poll request
        setTimeout(function() { this._requestStatus(); }.bind(this), this._COLLECTING_DELAY);
        this._timer.start(this._TIME_LIMIT_IN_MS);
      },
      columnData: function(colId, values, timeStamp){
        var column = this._columnsById[colId];
        if (timeStamp > column.receivedValuesTimeStamp) {
          column.data.length = 0;
          [].push.apply(column.data, values);
          column.receivedValuesTimeStamp = timeStamp;
          events.emit('data', colId);
        }
      },
      statusErrored: function() {
        this._timer.stop();
        this.transition('errored');
      },
      timeout: function() {
        this.transition('errored');
      }
    },
    controlDisabled: {
      _onEnter: function() {
        events.emit('controlDisabled');
        this._requestStatus();
        this._timer.start(this._TIME_LIMIT_IN_MS);
      },
      _onExit: function() {
        events.emit('controlEnabled');
      },
      statusReceived: function(response) {
        this._timer.stop();

        this._processStatus(response);

        if (response.collection.canControl) {
          this.transition('polling');
          return;
        }

        var currentlyAttached = typeof(response.currentInterface) === "undefined" || response.currentInterface === null || response.currentInterface !== "None Found";
        if (!currentlyAttached) {
          this.transition('interfaceMissing');
          return;
        }

        // Schedule the next poll request
        setTimeout(function() { this._requestStatus(); }.bind(this), this._POLLING_DELAY);
        this._timer.start(this._TIME_LIMIT_IN_MS);
      },
      statusErrored: function() {
        this._timer.stop();
        this.transition('errored');
      },
      timeout: function() {
        this.transition('errored');
      }
    },
    interfaceMissing: {
      _onEnter: function() {
        events.emit('interfaceRemoved');
        this._requestStatus();
        this._timer.start(this._TIME_LIMIT_IN_MS);
      },
      _onExit: function() {
        events.emit('interfaceConnected');
      },
      statusReceived: function(response) {
        this._timer.stop();

        this._processStatus(response);

        var currentlyAttached = typeof(response.currentInterface) === "undefined" || response.currentInterface === null || response.currentInterface !== "None Found";
        if (currentlyAttached) {
          if (response.collection.canControl) {
            this.transition('polling');
          } else {
            this.transition('controlDisabled');
          }
          return;
        }

        // Schedule the next poll request
        setTimeout(function() { this._requestStatus(); }.bind(this), this._POLLING_DELAY);
        this._timer.start(this._TIME_LIMIT_IN_MS);
      },
      statusErrored: function() {
        this._timer.stop();
        this.transition('errored');
      },
      timeout: function() {
        this.transition('errored');
      }
    },
    errored: {
      _onEnter: function() {
        events.emit('statusErrored');
      }
    },
    launchTimedOut: {
      _onEnter: function() {
        events.emit('launchTimedOut');
      }
    },
    unsupported: {
      _onEnter: function() {
        events.emit('statusErrored'); // FIXME
      }
    }
  },

  setHosts: function(hosts) {
    this._availableHosts = _.isArray(hosts) ? hosts : [hosts];
    this._hostCycler.reset();
  },

  getParam: function(param) {
    return this._rawQueryParams[param];
  },

  setParam: function(param, value) {
    this._setRawQueryParams(param, value);
    this._updateQueryParams();
  },

  _setRawQueryParams: function(k, v) {
    if (v == null) {
      delete this._rawQueryParams[k];
    } else {
      this._rawQueryParams[k] = v;
    }
  },

  _updateQueryParams: function() {
    var v;
    Object.keys(this._rawQueryParams).forEach(function(k,i) {
      v = this._rawQueryParams[k];
      if (i === 0) {
        this.urlQueryParams = '?'+k+'='+v;
      } else {
        this.urlQueryParams += '&'+k+'='+v;
      }

    }.bind(this));
  },

  _requestStatus: function() {
    var xhr = this._createCORSRequest('GET', '/status'),
      fsm = this;
    // TODO set xhr timeout

    if (!xhr) {
      this.transition('unsupported');
      return;
    }

    xhr.onerror = function() {
      fsm.handle('statusErrored');
    };
    xhr.onload = function() {
      var response = this.response || JSON.parse(this.responseText); // jshint ignore:line
      if (typeof(response) === "string") { response = JSON.parse(response); }

      fsm.handle('statusReceived', response);
    };
    xhr.send();
  },



  _initializeSession: function() {
    this.datasets = [];
    this._datasetsById = Object.create(null);
    this._columnsById = Object.create(null);
    this._sessionChangedEmitted = false;
  },

  // Return false to abort further processing.
  _processStatus: function(response) {
    if (response.requestTimeStamp < this._lastStatusTimeStamp) {
      // stale out-of-order response; drop it like we never got it.
      return false;
    }

    this._lastStatusTimeStamp = response.requestTimeStamp;

    if ( ! this._currentSessionID ) {
      this._currentSessionID = response.sessionID;
      this._initializeSession();
    } else if (this._currentSessionID !== response.sessionID) {
      // Session ID changed on us unexpectedly. Client should probably stop polling, start polling.
      if ( ! this._sessionChangedEmitted) {
        events.emit('sessionChanged');
        this._sessionChangedEmitted = true;
      }
      return false;
    }
    this._processDatasets(response.sets);
    this._processColumns(response.columns);

    // TODO liveValue

    this.inControl = response.collection.inControl;

    events.emit('statusReceived');

    return true;
  },

  // Handle 'datasets' and 'columns' in the response
  _processDatasets: function(sets) {
    Object.keys(sets).forEach(function(setId) {
      if ( ! this._datasetsById[setId] ) {
        // mind, no datasetAdded is emitted until the second collection because the first
        // dataset always exists
        events.emit('datasetAdded', setId);
        this._datasetsById[setId] = {
          columns: [],
          id: setId
        };
        this.datasets.unshift(this._datasetsById[setId]);
      }
      // Set the columns array length so that it's the correct size if a column was removed
      this._datasetsById[setId].columns.length = sets[setId].colIDs.length;
    }.bind(this));
    // make sure the highest-numbered dataset is always datasets[0]
    this.datasets.sort(function(d1, d2) { return d2.setId-d1.setId; });
  },

  _processColumns: function(cols) {
    // looks familiar
    var eventsToEmit = [];
    Object.keys(cols).forEach(function(colId) {
      var columnFromResponse = cols[colId];
      var dataset = this._datasetsById[columnFromResponse.setID];
      var column = this._columnsById[colId];

      if ( ! column ) {
        eventsToEmit.push(['columnAdded',colId]);
        // Remember, the column information can change
        // HOWEVER, assume a column is never removed from one dataset and added to another
        column = this._columnsById[colId] = {
          id: null,
          name: null,
          units: null,
          receivedValuesTimeStamp: 0,
          requestedValuesTimeStamp: 0,
          liveValueTimeStamp: 0,
          liveValue: null,
          data: []
        };
      } else if (column !== dataset.columns[columnFromResponse.position]) {
        eventsToEmit.push(['columnMoved',colId]);
      }

      dataset.columns[columnFromResponse.position] = column;

      if (column.units !== null && column.units !== columnFromResponse.units) {
        eventsToEmit.push(['columnTypeChanged',colId]);
      }

      column.units = columnFromResponse.units;
      column.name = columnFromResponse.name;
      column.id = colId;
      column.liveValue = parseFloat(columnFromResponse.liveValue || 0);
      column.liveValueTimeStamp = columnFromResponse.liveValueTimeStamp;

      if (column.requestedValuesTimeStamp < columnFromResponse.valuesTimeStamp) {
        this._requestData(colId, columnFromResponse.valuesTimeStamp);
        column.requestedValuesTimeStamp = columnFromResponse.valuesTimeStamp;
      }
    }.bind(this));

    // Find columns that were removed.
    Object.keys(this._columnsById).forEach(function(colId) {
      if ( ! cols[colId] ) {
        eventsToEmit.push(['columnRemoved', colId]);
        delete this._columnsById[colId];
      }
    }.bind(this));

    eventsToEmit.forEach(function(arr) {
      events.emit(arr[0], arr[1]);
    });
  },

  // Request data if status indicates there's more data
  _requestData: function(colId, timeStamp) {
    var xhr = this._createCORSRequest('GET', '/columns/' + colId),
      fsm = this;
    // look, we wouldn't have got here if we didn't support CORS
    xhr.send();

    xhr.onload = function() {
      var response = this.response || JSON.parse(this.responseText);
      if (typeof(response) === "string") { response = JSON.parse(response); }
      var values = response.values;
      fsm.handle('columnData', colId, values, timeStamp);
    };
  },

  // see http://www.html5rocks.com/en/tutorials/cors/
  _createCORSRequest: function(method, relativeUrl) {
    var url = this.urlPrefix + relativeUrl + this.urlQueryParams;
    var xhr = new XMLHttpRequest();

    if ('withCredentials' in xhr) {
      xhr.open(method, url, true);
      xhr.responseType = 'json';
      xhr.setRequestHeader('Accept', 'application/json');
    } else if (typeof XDomainRequest !== 'undefined') {
      // IE8/9's XMLHttpRequest object doesn't support CORS; instead, you have to use an
      // 'XDomainRequest' object
      xhr = new XDomainRequest();
      // we can't set custom headers in IE9
      // see http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
      xhr.open(method, url);
    } else {
      return null;
    }

    return xhr;
  },

  promisifyRequest: function(url) {
    var fsm = this;
    return new RSVP.Promise(function(resolve, reject) {
      var xhr = fsm._createCORSRequest('GET', url);
      if ( ! xhr ) {
        reject(new Error("This browser does not appear to support Cross-Origin Resource Sharing"));
      }
      xhr.send();

      // Simply emitting errors isn't quite right because there's no way for the consumer
      // to tie the error to the particular start request
      xhr.onerror = function() {
        reject(this);
      };
      xhr.onload = resolve;
    });
  },

  _injectCcscFrame: function() {
    if (this._launchFrame !== null) {
      document.body.removeChild(this._launchFrame);
    }
    var obj = document.createElement('div');
    obj.id = 'sensor-connector-launch-frame-parent';
    obj.innerHTML = '<iframe id="sensor-connector-launch-frame" src="ccsc://sensorconnector.concord.org/"></iframe>';
    document.body.appendChild(obj);
    this._launchFrame = document.getElementById('sensor-connector-launch-frame-parent');
  }

});

function SensorConnectorInterface() {
  return {
    stateMachine: new SensorConnectorState(),

    startPolling: function(addresses, clientId, clientName) {
      this.stateMachine.setHosts(addresses);
      this.stateMachine.setParam('client', clientId);
      this.stateMachine.setParam('clientName', clientName);

      this.stateMachine.transition('connecting');
    },

    stopPolling: function() {
      this.stateMachine.transition('disconnected');
    },

    requestStart: function() { return this.stateMachine.promisifyRequest('/control/start'); },

    requestStop: function() { return this.stateMachine.promisifyRequest('/control/stop'); },

    on: function() {
      events.on.apply(events, arguments);
    },

    off: function() {
      events.off.apply(events, arguments);
    },

    get clientId() {
      return this.stateMachine.getParam('client');
    },

    set clientId(id) {
      this.stateMachine.setParam('client', id);
    },

    get clientName() {
      return this.stateMachine.getParam('clientName');
    },

    set clientName(name) {
      this.stateMachine.setParam('clientName', name);
    },

    get hasAttachedInterface() {
      return this.stateMachine.state !== 'interfaceMissing';
    },

    get datasets() {
      return this.stateMachine.datasets;
    },

    get isConnected() {
      return ['polling','collecting','controlDisabled','interfaceMissing'].indexOf(this.stateMachine.state) !== -1;
    },

    get isCollecting() {
      return this.stateMachine.state === 'collecting';
    },

    get inControl() {
      return this.stateMachine.state !== 'controlDisabled';
    },

    get launchTimedOut() {
      return this.stateMachine.state === 'launchTimedOut';
    },

    get canControl() {
      return this.stateMachine.state !== 'controlDisabled';
    }
  };
}

module.exports = SensorConnectorInterface;

},{"eventemitter2":3,"lodash":4,"machina":5,"rsvp":7}],2:[function(require,module,exports){
// shim for using process in browser

var process = module.exports = {};

process.nextTick = (function () {
    var canSetImmediate = typeof window !== 'undefined'
    && window.setImmediate;
    var canPost = typeof window !== 'undefined'
    && window.postMessage && window.addEventListener
    ;

    if (canSetImmediate) {
        return function (f) { return window.setImmediate(f) };
    }

    if (canPost) {
        var queue = [];
        window.addEventListener('message', function (ev) {
            var source = ev.source;
            if ((source === window || source === null) && ev.data === 'process-tick') {
                ev.stopPropagation();
                if (queue.length > 0) {
                    var fn = queue.shift();
                    fn();
                }
            }
        }, true);

        return function nextTick(fn) {
            queue.push(fn);
            window.postMessage('process-tick', '*');
        };
    }

    return function nextTick(fn) {
        setTimeout(fn, 0);
    };
})();

process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];

process.binding = function (name) {
    throw new Error('process.binding is not supported');
}

// TODO(shtylman)
process.cwd = function () { return '/' };
process.chdir = function (dir) {
    throw new Error('process.chdir is not supported');
};

},{}],3:[function(require,module,exports){
/*!
 * EventEmitter2
 * https://github.com/hij1nx/EventEmitter2
 *
 * Copyright (c) 2013 hij1nx
 * Licensed under the MIT license.
 */
;!function(undefined) {

  var isArray = Array.isArray ? Array.isArray : function _isArray(obj) {
    return Object.prototype.toString.call(obj) === "[object Array]";
  };
  var defaultMaxListeners = 10;

  function init() {
    this._events = {};
    if (this._conf) {
      configure.call(this, this._conf);
    }
  }

  function configure(conf) {
    if (conf) {

      this._conf = conf;

      conf.delimiter && (this.delimiter = conf.delimiter);
      conf.maxListeners && (this._events.maxListeners = conf.maxListeners);
      conf.wildcard && (this.wildcard = conf.wildcard);
      conf.newListener && (this.newListener = conf.newListener);

      if (this.wildcard) {
        this.listenerTree = {};
      }
    }
  }

  function EventEmitter(conf) {
    this._events = {};
    this.newListener = false;
    configure.call(this, conf);
  }

  //
  // Attention, function return type now is array, always !
  // It has zero elements if no any matches found and one or more
  // elements (leafs) if there are matches
  //
  function searchListenerTree(handlers, type, tree, i) {
    if (!tree) {
      return [];
    }
    var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached,
        typeLength = type.length, currentType = type[i], nextType = type[i+1];
    if (i === typeLength && tree._listeners) {
      //
      // If at the end of the event(s) list and the tree has listeners
      // invoke those listeners.
      //
      if (typeof tree._listeners === 'function') {
        handlers && handlers.push(tree._listeners);
        return [tree];
      } else {
        for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) {
          handlers && handlers.push(tree._listeners[leaf]);
        }
        return [tree];
      }
    }

    if ((currentType === '*' || currentType === '**') || tree[currentType]) {
      //
      // If the event emitted is '*' at this part
      // or there is a concrete match at this patch
      //
      if (currentType === '*') {
        for (branch in tree) {
          if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
            listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1));
          }
        }
        return listeners;
      } else if(currentType === '**') {
        endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*'));
        if(endReached && tree._listeners) {
          // The next element has a _listeners, add it to the handlers.
          listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength));
        }

        for (branch in tree) {
          if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
            if(branch === '*' || branch === '**') {
              if(tree[branch]._listeners && !endReached) {
                listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength));
              }
              listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
            } else if(branch === nextType) {
              listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2));
            } else {
              // No match on this one, shift into the tree but not in the type array.
              listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
            }
          }
        }
        return listeners;
      }

      listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1));
    }

    xTree = tree['*'];
    if (xTree) {
      //
      // If the listener tree will allow any match for this part,
      // then recursively explore all branches of the tree
      //
      searchListenerTree(handlers, type, xTree, i+1);
    }

    xxTree = tree['**'];
    if(xxTree) {
      if(i < typeLength) {
        if(xxTree._listeners) {
          // If we have a listener on a '**', it will catch all, so add its handler.
          searchListenerTree(handlers, type, xxTree, typeLength);
        }

        // Build arrays of matching next branches and others.
        for(branch in xxTree) {
          if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) {
            if(branch === nextType) {
              // We know the next element will match, so jump twice.
              searchListenerTree(handlers, type, xxTree[branch], i+2);
            } else if(branch === currentType) {
              // Current node matches, move into the tree.
              searchListenerTree(handlers, type, xxTree[branch], i+1);
            } else {
              isolatedBranch = {};
              isolatedBranch[branch] = xxTree[branch];
              searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1);
            }
          }
        }
      } else if(xxTree._listeners) {
        // We have reached the end and still on a '**'
        searchListenerTree(handlers, type, xxTree, typeLength);
      } else if(xxTree['*'] && xxTree['*']._listeners) {
        searchListenerTree(handlers, type, xxTree['*'], typeLength);
      }
    }

    return listeners;
  }

  function growListenerTree(type, listener) {

    type = typeof type === 'string' ? type.split(this.delimiter) : type.slice();

    //
    // Looks for two consecutive '**', if so, don't add the event at all.
    //
    for(var i = 0, len = type.length; i+1 < len; i++) {
      if(type[i] === '**' && type[i+1] === '**') {
        return;
      }
    }

    var tree = this.listenerTree;
    var name = type.shift();

    while (name) {

      if (!tree[name]) {
        tree[name] = {};
      }

      tree = tree[name];

      if (type.length === 0) {

        if (!tree._listeners) {
          tree._listeners = listener;
        }
        else if(typeof tree._listeners === 'function') {
          tree._listeners = [tree._listeners, listener];
        }
        else if (isArray(tree._listeners)) {

          tree._listeners.push(listener);

          if (!tree._listeners.warned) {

            var m = defaultMaxListeners;

            if (typeof this._events.maxListeners !== 'undefined') {
              m = this._events.maxListeners;
            }

            if (m > 0 && tree._listeners.length > m) {

              tree._listeners.warned = true;
              console.error('(node) warning: possible EventEmitter memory ' +
                            'leak detected. %d listeners added. ' +
                            'Use emitter.setMaxListeners() to increase limit.',
                            tree._listeners.length);
              console.trace();
            }
          }
        }
        return true;
      }
      name = type.shift();
    }
    return true;
  }

  // By default EventEmitters will print a warning if more than
  // 10 listeners are added to it. This is a useful default which
  // helps finding memory leaks.
  //
  // Obviously not all Emitters should be limited to 10. This function allows
  // that to be increased. Set to zero for unlimited.

  EventEmitter.prototype.delimiter = '.';

  EventEmitter.prototype.setMaxListeners = function(n) {
    this._events || init.call(this);
    this._events.maxListeners = n;
    if (!this._conf) this._conf = {};
    this._conf.maxListeners = n;
  };

  EventEmitter.prototype.event = '';

  EventEmitter.prototype.once = function(event, fn) {
    this.many(event, 1, fn);
    return this;
  };

  EventEmitter.prototype.many = function(event, ttl, fn) {
    var self = this;

    if (typeof fn !== 'function') {
      throw new Error('many only accepts instances of Function');
    }

    function listener() {
      if (--ttl === 0) {
        self.off(event, listener);
      }
      fn.apply(this, arguments);
    }

    listener._origin = fn;

    this.on(event, listener);

    return self;
  };

  EventEmitter.prototype.emit = function() {

    this._events || init.call(this);

    var type = arguments[0];

    if (type === 'newListener' && !this.newListener) {
      if (!this._events.newListener) { return false; }
    }

    // Loop through the *_all* functions and invoke them.
    if (this._all) {
      var l = arguments.length;
      var args = new Array(l - 1);
      for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
      for (i = 0, l = this._all.length; i < l; i++) {
        this.event = type;
        this._all[i].apply(this, args);
      }
    }

    // If there is no 'error' event listener then throw.
    if (type === 'error') {

      if (!this._all &&
        !this._events.error &&
        !(this.wildcard && this.listenerTree.error)) {

        if (arguments[1] instanceof Error) {
          throw arguments[1]; // Unhandled 'error' event
        } else {
          throw new Error("Uncaught, unspecified 'error' event.");
        }
        return false;
      }
    }

    var handler;

    if(this.wildcard) {
      handler = [];
      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
      searchListenerTree.call(this, handler, ns, this.listenerTree, 0);
    }
    else {
      handler = this._events[type];
    }

    if (typeof handler === 'function') {
      this.event = type;
      if (arguments.length === 1) {
        handler.call(this);
      }
      else if (arguments.length > 1)
        switch (arguments.length) {
          case 2:
            handler.call(this, arguments[1]);
            break;
          case 3:
            handler.call(this, arguments[1], arguments[2]);
            break;
          // slower
          default:
            var l = arguments.length;
            var args = new Array(l - 1);
            for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
            handler.apply(this, args);
        }
      return true;
    }
    else if (handler) {
      var l = arguments.length;
      var args = new Array(l - 1);
      for (var i = 1; i < l; i++) args[i - 1] = arguments[i];

      var listeners = handler.slice();
      for (var i = 0, l = listeners.length; i < l; i++) {
        this.event = type;
        listeners[i].apply(this, args);
      }
      return (listeners.length > 0) || !!this._all;
    }
    else {
      return !!this._all;
    }

  };

  EventEmitter.prototype.on = function(type, listener) {

    if (typeof type === 'function') {
      this.onAny(type);
      return this;
    }

    if (typeof listener !== 'function') {
      throw new Error('on only accepts instances of Function');
    }
    this._events || init.call(this);

    // To avoid recursion in the case that type == "newListeners"! Before
    // adding it to the listeners, first emit "newListeners".
    this.emit('newListener', type, listener);

    if(this.wildcard) {
      growListenerTree.call(this, type, listener);
      return this;
    }

    if (!this._events[type]) {
      // Optimize the case of one listener. Don't need the extra array object.
      this._events[type] = listener;
    }
    else if(typeof this._events[type] === 'function') {
      // Adding the second element, need to change to array.
      this._events[type] = [this._events[type], listener];
    }
    else if (isArray(this._events[type])) {
      // If we've already got an array, just append.
      this._events[type].push(listener);

      // Check for listener leak
      if (!this._events[type].warned) {

        var m = defaultMaxListeners;

        if (typeof this._events.maxListeners !== 'undefined') {
          m = this._events.maxListeners;
        }

        if (m > 0 && this._events[type].length > m) {

          this._events[type].warned = true;
          console.error('(node) warning: possible EventEmitter memory ' +
                        'leak detected. %d listeners added. ' +
                        'Use emitter.setMaxListeners() to increase limit.',
                        this._events[type].length);
          console.trace();
        }
      }
    }
    return this;
  };

  EventEmitter.prototype.onAny = function(fn) {

    if (typeof fn !== 'function') {
      throw new Error('onAny only accepts instances of Function');
    }

    if(!this._all) {
      this._all = [];
    }

    // Add the function to the event listener collection.
    this._all.push(fn);
    return this;
  };

  EventEmitter.prototype.addListener = EventEmitter.prototype.on;

  EventEmitter.prototype.off = function(type, listener) {
    if (typeof listener !== 'function') {
      throw new Error('removeListener only takes instances of Function');
    }

    var handlers,leafs=[];

    if(this.wildcard) {
      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
      leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
    }
    else {
      // does not use listeners(), so no side effect of creating _events[type]
      if (!this._events[type]) return this;
      handlers = this._events[type];
      leafs.push({_listeners:handlers});
    }

    for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
      var leaf = leafs[iLeaf];
      handlers = leaf._listeners;
      if (isArray(handlers)) {

        var position = -1;

        for (var i = 0, length = handlers.length; i < length; i++) {
          if (handlers[i] === listener ||
            (handlers[i].listener && handlers[i].listener === listener) ||
            (handlers[i]._origin && handlers[i]._origin === listener)) {
            position = i;
            break;
          }
        }

        if (position < 0) {
          continue;
        }

        if(this.wildcard) {
          leaf._listeners.splice(position, 1);
        }
        else {
          this._events[type].splice(position, 1);
        }

        if (handlers.length === 0) {
          if(this.wildcard) {
            delete leaf._listeners;
          }
          else {
            delete this._events[type];
          }
        }
        return this;
      }
      else if (handlers === listener ||
        (handlers.listener && handlers.listener === listener) ||
        (handlers._origin && handlers._origin === listener)) {
        if(this.wildcard) {
          delete leaf._listeners;
        }
        else {
          delete this._events[type];
        }
      }
    }

    return this;
  };

  EventEmitter.prototype.offAny = function(fn) {
    var i = 0, l = 0, fns;
    if (fn && this._all && this._all.length > 0) {
      fns = this._all;
      for(i = 0, l = fns.length; i < l; i++) {
        if(fn === fns[i]) {
          fns.splice(i, 1);
          return this;
        }
      }
    } else {
      this._all = [];
    }
    return this;
  };

  EventEmitter.prototype.removeListener = EventEmitter.prototype.off;

  EventEmitter.prototype.removeAllListeners = function(type) {
    if (arguments.length === 0) {
      !this._events || init.call(this);
      return this;
    }

    if(this.wildcard) {
      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
      var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);

      for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
        var leaf = leafs[iLeaf];
        leaf._listeners = null;
      }
    }
    else {
      if (!this._events[type]) return this;
      this._events[type] = null;
    }
    return this;
  };

  EventEmitter.prototype.listeners = function(type) {
    if(this.wildcard) {
      var handlers = [];
      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
      searchListenerTree.call(this, handlers, ns, this.listenerTree, 0);
      return handlers;
    }

    this._events || init.call(this);

    if (!this._events[type]) this._events[type] = [];
    if (!isArray(this._events[type])) {
      this._events[type] = [this._events[type]];
    }
    return this._events[type];
  };

  EventEmitter.prototype.listenersAny = function() {

    if(this._all) {
      return this._all;
    }
    else {
      return [];
    }

  };

  if (typeof define === 'function' && define.amd) {
     // AMD. Register as an anonymous module.
    define(function() {
      return EventEmitter;
    });
  } else if (typeof exports === 'object') {
    // CommonJS
    exports.EventEmitter2 = EventEmitter;
  }
  else {
    // Browser global.
    window.EventEmitter2 = EventEmitter;
  }
}();

},{}],4:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/**
 * @license
 * lodash 3.10.0 (Custom Build) <https://lodash.com/>
 * Build: `lodash modern -d -o ./index.js`
 * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
 * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 * Available under MIT license <https://lodash.com/license>
 */
;(function() {

  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
  var undefined;

  /** Used as the semantic version number. */
  var VERSION = '3.10.0';

  /** Used to compose bitmasks for wrapper metadata. */
  var BIND_FLAG = 1,
      BIND_KEY_FLAG = 2,
      CURRY_BOUND_FLAG = 4,
      CURRY_FLAG = 8,
      CURRY_RIGHT_FLAG = 16,
      PARTIAL_FLAG = 32,
      PARTIAL_RIGHT_FLAG = 64,
      ARY_FLAG = 128,
      REARG_FLAG = 256;

  /** Used as default options for `_.trunc`. */
  var DEFAULT_TRUNC_LENGTH = 30,
      DEFAULT_TRUNC_OMISSION = '...';

  /** Used to detect when a function becomes hot. */
  var HOT_COUNT = 150,
      HOT_SPAN = 16;

  /** Used as the size to enable large array optimizations. */
  var LARGE_ARRAY_SIZE = 200;

  /** Used to indicate the type of lazy iteratees. */
  var LAZY_FILTER_FLAG = 1,
      LAZY_MAP_FLAG = 2;

  /** Used as the `TypeError` message for "Functions" methods. */
  var FUNC_ERROR_TEXT = 'Expected a function';

  /** Used as the internal argument placeholder. */
  var PLACEHOLDER = '__lodash_placeholder__';

  /** `Object#toString` result references. */
  var argsTag = '[object Arguments]',
      arrayTag = '[object Array]',
      boolTag = '[object Boolean]',
      dateTag = '[object Date]',
      errorTag = '[object Error]',
      funcTag = '[object Function]',
      mapTag = '[object Map]',
      numberTag = '[object Number]',
      objectTag = '[object Object]',
      regexpTag = '[object RegExp]',
      setTag = '[object Set]',
      stringTag = '[object String]',
      weakMapTag = '[object WeakMap]';

  var arrayBufferTag = '[object ArrayBuffer]',
      float32Tag = '[object Float32Array]',
      float64Tag = '[object Float64Array]',
      int8Tag = '[object Int8Array]',
      int16Tag = '[object Int16Array]',
      int32Tag = '[object Int32Array]',
      uint8Tag = '[object Uint8Array]',
      uint8ClampedTag = '[object Uint8ClampedArray]',
      uint16Tag = '[object Uint16Array]',
      uint32Tag = '[object Uint32Array]';

  /** Used to match empty string literals in compiled template source. */
  var reEmptyStringLeading = /\b__p \+= '';/g,
      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;

  /** Used to match HTML entities and HTML characters. */
  var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g,
      reUnescapedHtml = /[&<>"'`]/g,
      reHasEscapedHtml = RegExp(reEscapedHtml.source),
      reHasUnescapedHtml = RegExp(reUnescapedHtml.source);

  /** Used to match template delimiters. */
  var reEscape = /<%-([\s\S]+?)%>/g,
      reEvaluate = /<%([\s\S]+?)%>/g,
      reInterpolate = /<%=([\s\S]+?)%>/g;

  /** Used to match property names within property paths. */
  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,
      reIsPlainProp = /^\w*$/,
      rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g;

  /**
   * Used to match `RegExp` [syntax characters](http://ecma-international.org/ecma-262/6.0/#sec-patterns)
   * and those outlined by [`EscapeRegExpPattern`](http://ecma-international.org/ecma-262/6.0/#sec-escaperegexppattern).
   */
  var reRegExpChars = /^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,
      reHasRegExpChars = RegExp(reRegExpChars.source);

  /** Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). */
  var reComboMark = /[\u0300-\u036f\ufe20-\ufe23]/g;

  /** Used to match backslashes in property paths. */
  var reEscapeChar = /\\(\\)?/g;

  /** Used to match [ES template delimiters](http://ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components). */
  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;

  /** Used to match `RegExp` flags from their coerced string values. */
  var reFlags = /\w*$/;

  /** Used to detect hexadecimal string values. */
  var reHasHexPrefix = /^0[xX]/;

  /** Used to detect host constructors (Safari > 5). */
  var reIsHostCtor = /^\[object .+?Constructor\]$/;

  /** Used to detect unsigned integer values. */
  var reIsUint = /^\d+$/;

  /** Used to match latin-1 supplementary letters (excluding mathematical operators). */
  var reLatin1 = /[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g;

  /** Used to ensure capturing order of template delimiters. */
  var reNoMatch = /($^)/;

  /** Used to match unescaped characters in compiled string literals. */
  var reUnescapedString = /['\n\r\u2028\u2029\\]/g;

  /** Used to match words to create compound words. */
  var reWords = (function() {
    var upper = '[A-Z\\xc0-\\xd6\\xd8-\\xde]',
        lower = '[a-z\\xdf-\\xf6\\xf8-\\xff]+';

    return RegExp(upper + '+(?=' + upper + lower + ')|' + upper + '?' + lower + '|' + upper + '+|[0-9]+', 'g');
  }());

  /** Used to assign default `context` object properties. */
  var contextProps = [
    'Array', 'ArrayBuffer', 'Date', 'Error', 'Float32Array', 'Float64Array',
    'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Math', 'Number',
    'Object', 'RegExp', 'Set', 'String', '_', 'clearTimeout', 'isFinite',
    'parseFloat', 'parseInt', 'setTimeout', 'TypeError', 'Uint8Array',
    'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap'
  ];

  /** Used to make template sourceURLs easier to identify. */
  var templateCounter = -1;

  /** Used to identify `toStringTag` values of typed arrays. */
  var typedArrayTags = {};
  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
  typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
  typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
  typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
  typedArrayTags[uint32Tag] = true;
  typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
  typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
  typedArrayTags[dateTag] = typedArrayTags[errorTag] =
  typedArrayTags[funcTag] = typedArrayTags[mapTag] =
  typedArrayTags[numberTag] = typedArrayTags[objectTag] =
  typedArrayTags[regexpTag] = typedArrayTags[setTag] =
  typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;

  /** Used to identify `toStringTag` values supported by `_.clone`. */
  var cloneableTags = {};
  cloneableTags[argsTag] = cloneableTags[arrayTag] =
  cloneableTags[arrayBufferTag] = cloneableTags[boolTag] =
  cloneableTags[dateTag] = cloneableTags[float32Tag] =
  cloneableTags[float64Tag] = cloneableTags[int8Tag] =
  cloneableTags[int16Tag] = cloneableTags[int32Tag] =
  cloneableTags[numberTag] = cloneableTags[objectTag] =
  cloneableTags[regexpTag] = cloneableTags[stringTag] =
  cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
  cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
  cloneableTags[errorTag] = cloneableTags[funcTag] =
  cloneableTags[mapTag] = cloneableTags[setTag] =
  cloneableTags[weakMapTag] = false;

  /** Used to map latin-1 supplementary letters to basic latin letters. */
  var deburredLetters = {
    '\xc0': 'A',  '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
    '\xe0': 'a',  '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
    '\xc7': 'C',  '\xe7': 'c',
    '\xd0': 'D',  '\xf0': 'd',
    '\xc8': 'E',  '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
    '\xe8': 'e',  '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
    '\xcC': 'I',  '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
    '\xeC': 'i',  '\xed': 'i', '\xee': 'i', '\xef': 'i',
    '\xd1': 'N',  '\xf1': 'n',
    '\xd2': 'O',  '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
    '\xf2': 'o',  '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
    '\xd9': 'U',  '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
    '\xf9': 'u',  '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
    '\xdd': 'Y',  '\xfd': 'y', '\xff': 'y',
    '\xc6': 'Ae', '\xe6': 'ae',
    '\xde': 'Th', '\xfe': 'th',
    '\xdf': 'ss'
  };

  /** Used to map characters to HTML entities. */
  var htmlEscapes = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;',
    '`': '&#96;'
  };

  /** Used to map HTML entities to characters. */
  var htmlUnescapes = {
    '&amp;': '&',
    '&lt;': '<',
    '&gt;': '>',
    '&quot;': '"',
    '&#39;': "'",
    '&#96;': '`'
  };

  /** Used to determine if values are of the language type `Object`. */
  var objectTypes = {
    'function': true,
    'object': true
  };

  /** Used to escape characters for inclusion in compiled regexes. */
  var regexpEscapes = {
    '0': 'x30', '1': 'x31', '2': 'x32', '3': 'x33', '4': 'x34',
    '5': 'x35', '6': 'x36', '7': 'x37', '8': 'x38', '9': 'x39',
    'A': 'x41', 'B': 'x42', 'C': 'x43', 'D': 'x44', 'E': 'x45', 'F': 'x46',
    'a': 'x61', 'b': 'x62', 'c': 'x63', 'd': 'x64', 'e': 'x65', 'f': 'x66',
    'n': 'x6e', 'r': 'x72', 't': 'x74', 'u': 'x75', 'v': 'x76', 'x': 'x78'
  };

  /** Used to escape characters for inclusion in compiled string literals. */
  var stringEscapes = {
    '\\': '\\',
    "'": "'",
    '\n': 'n',
    '\r': 'r',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  /** Detect free variable `exports`. */
  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;

  /** Detect free variable `module`. */
  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;

  /** Detect free variable `global` from Node.js. */
  var freeGlobal = freeExports && freeModule && typeof global == 'object' && global && global.Object && global;

  /** Detect free variable `self`. */
  var freeSelf = objectTypes[typeof self] && self && self.Object && self;

  /** Detect free variable `window`. */
  var freeWindow = objectTypes[typeof window] && window && window.Object && window;

  /** Detect the popular CommonJS extension `module.exports`. */
  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;

  /**
   * Used as a reference to the global object.
   *
   * The `this` value is used if it's the global object to avoid Greasemonkey's
   * restricted `window` object, otherwise the `window` object is used.
   */
  var root = freeGlobal || ((freeWindow !== (this && this.window)) && freeWindow) || freeSelf || this;

  /*--------------------------------------------------------------------------*/

  /**
   * The base implementation of `compareAscending` which compares values and
   * sorts them in ascending order without guaranteeing a stable sort.
   *
   * @private
   * @param {*} value The value to compare.
   * @param {*} other The other value to compare.
   * @returns {number} Returns the sort order indicator for `value`.
   */
  function baseCompareAscending(value, other) {
    if (value !== other) {
      var valIsNull = value === null,
          valIsUndef = value === undefined,
          valIsReflexive = value === value;

      var othIsNull = other === null,
          othIsUndef = other === undefined,
          othIsReflexive = other === other;

      if ((value > other && !othIsNull) || !valIsReflexive ||
          (valIsNull && !othIsUndef && othIsReflexive) ||
          (valIsUndef && othIsReflexive)) {
        return 1;
      }
      if ((value < other && !valIsNull) || !othIsReflexive ||
          (othIsNull && !valIsUndef && valIsReflexive) ||
          (othIsUndef && valIsReflexive)) {
        return -1;
      }
    }
    return 0;
  }

  /**
   * The base implementation of `_.findIndex` and `_.findLastIndex` without
   * support for callback shorthands and `this` binding.
   *
   * @private
   * @param {Array} array The array to search.
   * @param {Function} predicate The function invoked per iteration.
   * @param {boolean} [fromRight] Specify iterating from right to left.
   * @returns {number} Returns the index of the matched value, else `-1`.
   */
  function baseFindIndex(array, predicate, fromRight) {
    var length = array.length,
        index = fromRight ? length : -1;

    while ((fromRight ? index-- : ++index < length)) {
      if (predicate(array[index], index, array)) {
        return index;
      }
    }
    return -1;
  }

  /**
   * The base implementation of `_.indexOf` without support for binary searches.
   *
   * @private
   * @param {Array} array The array to search.
   * @param {*} value The value to search for.
   * @param {number} fromIndex The index to search from.
   * @returns {number} Returns the index of the matched value, else `-1`.
   */
  function baseIndexOf(array, value, fromIndex) {
    if (value !== value) {
      return indexOfNaN(array, fromIndex);
    }
    var index = fromIndex - 1,
        length = array.length;

    while (++index < length) {
      if (array[index] === value) {
        return index;
      }
    }
    return -1;
  }

  /**
   * The base implementation of `_.isFunction` without support for environments
   * with incorrect `typeof` results.
   *
   * @private
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
   */
  function baseIsFunction(value) {
    // Avoid a Chakra JIT bug in compatibility modes of IE 11.
    // See https://github.com/jashkenas/underscore/issues/1621 for more details.
    return typeof value == 'function' || false;
  }

  /**
   * Converts `value` to a string if it's not one. An empty string is returned
   * for `null` or `undefined` values.
   *
   * @private
   * @param {*} value The value to process.
   * @returns {string} Returns the string.
   */
  function baseToString(value) {
    return value == null ? '' : (value + '');
  }

  /**
   * Used by `_.trim` and `_.trimLeft` to get the index of the first character
   * of `string` that is not found in `chars`.
   *
   * @private
   * @param {string} string The string to inspect.
   * @param {string} chars The characters to find.
   * @returns {number} Returns the index of the first character not found in `chars`.
   */
  function charsLeftIndex(string, chars) {
    var index = -1,
        length = string.length;

    while (++index < length && chars.indexOf(string.charAt(index)) > -1) {}
    return index;
  }

  /**
   * Used by `_.trim` and `_.trimRight` to get the index of the last character
   * of `string` that is not found in `chars`.
   *
   * @private
   * @param {string} string The string to inspect.
   * @param {string} chars The characters to find.
   * @returns {number} Returns the index of the last character not found in `chars`.
   */
  function charsRightIndex(string, chars) {
    var index = string.length;

    while (index-- && chars.indexOf(string.charAt(index)) > -1) {}
    return index;
  }

  /**
   * Used by `_.sortBy` to compare transformed elements of a collection and stable
   * sort them in ascending order.
   *
   * @private
   * @param {Object} object The object to compare.
   * @param {Object} other The other object to compare.
   * @returns {number} Returns the sort order indicator for `object`.
   */
  function compareAscending(object, other) {
    return baseCompareAscending(object.criteria, other.criteria) || (object.index - other.index);
  }

  /**
   * Used by `_.sortByOrder` to compare multiple properties of a value to another
   * and stable sort them.
   *
   * If `orders` is unspecified, all valuess are sorted in ascending order. Otherwise,
   * a value is sorted in ascending order if its corresponding order is "asc", and
   * descending if "desc".
   *
   * @private
   * @param {Object} object The object to compare.
   * @param {Object} other The other object to compare.
   * @param {boolean[]} orders The order to sort by for each property.
   * @returns {number} Returns the sort order indicator for `object`.
   */
  function compareMultiple(object, other, orders) {
    var index = -1,
        objCriteria = object.criteria,
        othCriteria = other.criteria,
        length = objCriteria.length,
        ordersLength = orders.length;

    while (++index < length) {
      var result = baseCompareAscending(objCriteria[index], othCriteria[index]);
      if (result) {
        if (index >= ordersLength) {
          return result;
        }
        var order = orders[index];
        return result * ((order === 'asc' || order === true) ? 1 : -1);
      }
    }
    // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
    // that causes it, under certain circumstances, to provide the same value for
    // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
    // for more details.
    //
    // This also ensures a stable sort in V8 and other engines.
    // See https://code.google.com/p/v8/issues/detail?id=90 for more details.
    return object.index - other.index;
  }

  /**
   * Used by `_.deburr` to convert latin-1 supplementary letters to basic latin letters.
   *
   * @private
   * @param {string} letter The matched letter to deburr.
   * @returns {string} Returns the deburred letter.
   */
  function deburrLetter(letter) {
    return deburredLetters[letter];
  }

  /**
   * Used by `_.escape` to convert characters to HTML entities.
   *
   * @private
   * @param {string} chr The matched character to escape.
   * @returns {string} Returns the escaped character.
   */
  function escapeHtmlChar(chr) {
    return htmlEscapes[chr];
  }

  /**
   * Used by `_.escapeRegExp` to escape characters for inclusion in compiled regexes.
   *
   * @private
   * @param {string} chr The matched character to escape.
   * @param {string} leadingChar The capture group for a leading character.
   * @param {string} whitespaceChar The capture group for a whitespace character.
   * @returns {string} Returns the escaped character.
   */
  function escapeRegExpChar(chr, leadingChar, whitespaceChar) {
    if (leadingChar) {
      chr = regexpEscapes[chr];
    } else if (whitespaceChar) {
      chr = stringEscapes[chr];
    }
    return '\\' + chr;
  }

  /**
   * Used by `_.template` to escape characters for inclusion in compiled string literals.
   *
   * @private
   * @param {string} chr The matched character to escape.
   * @returns {string} Returns the escaped character.
   */
  function escapeStringChar(chr) {
    return '\\' + stringEscapes[chr];
  }

  /**
   * Gets the index at which the first occurrence of `NaN` is found in `array`.
   *
   * @private
   * @param {Array} array The array to search.
   * @param {number} fromIndex The index to search from.
   * @param {boolean} [fromRight] Specify iterating from right to left.
   * @returns {number} Returns the index of the matched `NaN`, else `-1`.
   */
  function indexOfNaN(array, fromIndex, fromRight) {
    var length = array.length,
        index = fromIndex + (fromRight ? 0 : -1);

    while ((fromRight ? index-- : ++index < length)) {
      var other = array[index];
      if (other !== other) {
        return index;
      }
    }
    return -1;
  }

  /**
   * Checks if `value` is object-like.
   *
   * @private
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
   */
  function isObjectLike(value) {
    return !!value && typeof value == 'object';
  }

  /**
   * Used by `trimmedLeftIndex` and `trimmedRightIndex` to determine if a
   * character code is whitespace.
   *
   * @private
   * @param {number} charCode The character code to inspect.
   * @returns {boolean} Returns `true` if `charCode` is whitespace, else `false`.
   */
  function isSpace(charCode) {
    return ((charCode <= 160 && (charCode >= 9 && charCode <= 13) || charCode == 32 || charCode == 160) || charCode == 5760 || charCode == 6158 ||
      (charCode >= 8192 && (charCode <= 8202 || charCode == 8232 || charCode == 8233 || charCode == 8239 || charCode == 8287 || charCode == 12288 || charCode == 65279)));
  }

  /**
   * Replaces all `placeholder` elements in `array` with an internal placeholder
   * and returns an array of their indexes.
   *
   * @private
   * @param {Array} array The array to modify.
   * @param {*} placeholder The placeholder to replace.
   * @returns {Array} Returns the new array of placeholder indexes.
   */
  function replaceHolders(array, placeholder) {
    var index = -1,
        length = array.length,
        resIndex = -1,
        result = [];

    while (++index < length) {
      if (array[index] === placeholder) {
        array[index] = PLACEHOLDER;
        result[++resIndex] = index;
      }
    }
    return result;
  }

  /**
   * An implementation of `_.uniq` optimized for sorted arrays without support
   * for callback shorthands and `this` binding.
   *
   * @private
   * @param {Array} array The array to inspect.
   * @param {Function} [iteratee] The function invoked per iteration.
   * @returns {Array} Returns the new duplicate-value-free array.
   */
  function sortedUniq(array, iteratee) {
    var seen,
        index = -1,
        length = array.length,
        resIndex = -1,
        result = [];

    while (++index < length) {
      var value = array[index],
          computed = iteratee ? iteratee(value, index, array) : value;

      if (!index || seen !== computed) {
        seen = computed;
        result[++resIndex] = value;
      }
    }
    return result;
  }

  /**
   * Used by `_.trim` and `_.trimLeft` to get the index of the first non-whitespace
   * character of `string`.
   *
   * @private
   * @param {string} string The string to inspect.
   * @returns {number} Returns the index of the first non-whitespace character.
   */
  function trimmedLeftIndex(string) {
    var index = -1,
        length = string.length;

    while (++index < length && isSpace(string.charCodeAt(index))) {}
    return index;
  }

  /**
   * Used by `_.trim` and `_.trimRight` to get the index of the last non-whitespace
   * character of `string`.
   *
   * @private
   * @param {string} string The string to inspect.
   * @returns {number} Returns the index of the last non-whitespace character.
   */
  function trimmedRightIndex(string) {
    var index = string.length;

    while (index-- && isSpace(string.charCodeAt(index))) {}
    return index;
  }

  /**
   * Used by `_.unescape` to convert HTML entities to characters.
   *
   * @private
   * @param {string} chr The matched character to unescape.
   * @returns {string} Returns the unescaped character.
   */
  function unescapeHtmlChar(chr) {
    return htmlUnescapes[chr];
  }

  /*--------------------------------------------------------------------------*/

  /**
   * Create a new pristine `lodash` function using the given `context` object.
   *
   * @static
   * @memberOf _
   * @category Utility
   * @param {Object} [context=root] The context object.
   * @returns {Function} Returns a new `lodash` function.
   * @example
   *
   * _.mixin({ 'foo': _.constant('foo') });
   *
   * var lodash = _.runInContext();
   * lodash.mixin({ 'bar': lodash.constant('bar') });
   *
   * _.isFunction(_.foo);
   * // => true
   * _.isFunction(_.bar);
   * // => false
   *
   * lodash.isFunction(lodash.foo);
   * // => false
   * lodash.isFunction(lodash.bar);
   * // => true
   *
   * // using `context` to mock `Date#getTime` use in `_.now`
   * var mock = _.runInContext({
   *   'Date': function() {
   *     return { 'getTime': getTimeMock };
   *   }
   * });
   *
   * // or creating a suped-up `defer` in Node.js
   * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
   */
  function runInContext(context) {
    // Avoid issues with some ES3 environments that attempt to use values, named
    // after built-in constructors like `Object`, for the creation of literals.
    // ES5 clears this up by stating that literals must use built-in constructors.
    // See https://es5.github.io/#x11.1.5 for more details.
    context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;

    /** Native constructor references. */
    var Array = context.Array,
        Date = context.Date,
        Error = context.Error,
        Function = context.Function,
        Math = context.Math,
        Number = context.Number,
        Object = context.Object,
        RegExp = context.RegExp,
        String = context.String,
        TypeError = context.TypeError;

    /** Used for native method references. */
    var arrayProto = Array.prototype,
        objectProto = Object.prototype,
        stringProto = String.prototype;

    /** Used to resolve the decompiled source of functions. */
    var fnToString = Function.prototype.toString;

    /** Used to check objects for own properties. */
    var hasOwnProperty = objectProto.hasOwnProperty;

    /** Used to generate unique IDs. */
    var idCounter = 0;

    /**
     * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
     * of values.
     */
    var objToString = objectProto.toString;

    /** Used to restore the original `_` reference in `_.noConflict`. */
    var oldDash = root._;

    /** Used to detect if a method is native. */
    var reIsNative = RegExp('^' +
      fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
      .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
    );

    /** Native method references. */
    var ArrayBuffer = context.ArrayBuffer,
        clearTimeout = context.clearTimeout,
        parseFloat = context.parseFloat,
        pow = Math.pow,
        propertyIsEnumerable = objectProto.propertyIsEnumerable,
        Set = getNative(context, 'Set'),
        setTimeout = context.setTimeout,
        splice = arrayProto.splice,
        Uint8Array = context.Uint8Array,
        WeakMap = getNative(context, 'WeakMap');

    /* Native method references for those with the same name as other `lodash` methods. */
    var nativeCeil = Math.ceil,
        nativeCreate = getNative(Object, 'create'),
        nativeFloor = Math.floor,
        nativeIsArray = getNative(Array, 'isArray'),
        nativeIsFinite = context.isFinite,
        nativeKeys = getNative(Object, 'keys'),
        nativeMax = Math.max,
        nativeMin = Math.min,
        nativeNow = getNative(Date, 'now'),
        nativeParseInt = context.parseInt,
        nativeRandom = Math.random;

    /** Used as references for `-Infinity` and `Infinity`. */
    var NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY,
        POSITIVE_INFINITY = Number.POSITIVE_INFINITY;

    /** Used as references for the maximum length and index of an array. */
    var MAX_ARRAY_LENGTH = 4294967295,
        MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
        HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;

    /**
     * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
     * of an array-like value.
     */
    var MAX_SAFE_INTEGER = 9007199254740991;

    /** Used to store function metadata. */
    var metaMap = WeakMap && new WeakMap;

    /** Used to lookup unminified function names. */
    var realNames = {};

    /*------------------------------------------------------------------------*/

    /**
     * Creates a `lodash` object which wraps `value` to enable implicit chaining.
     * Methods that operate on and return arrays, collections, and functions can
     * be chained together. Methods that retrieve a single value or may return a
     * primitive value will automatically end the chain returning the unwrapped
     * value. Explicit chaining may be enabled using `_.chain`. The execution of
     * chained methods is lazy, that is, execution is deferred until `_#value`
     * is implicitly or explicitly called.
     *
     * Lazy evaluation allows several methods to support shortcut fusion. Shortcut
     * fusion is an optimization strategy which merge iteratee calls; this can help
     * to avoid the creation of intermediate data structures and greatly reduce the
     * number of iteratee executions.
     *
     * Chaining is supported in custom builds as long as the `_#value` method is
     * directly or indirectly included in the build.
     *
     * In addition to lodash methods, wrappers have `Array` and `String` methods.
     *
     * The wrapper `Array` methods are:
     * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`,
     * `splice`, and `unshift`
     *
     * The wrapper `String` methods are:
     * `replace` and `split`
     *
     * The wrapper methods that support shortcut fusion are:
     * `compact`, `drop`, `dropRight`, `dropRightWhile`, `dropWhile`, `filter`,
     * `first`, `initial`, `last`, `map`, `pluck`, `reject`, `rest`, `reverse`,
     * `slice`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `toArray`,
     * and `where`
     *
     * The chainable wrapper methods are:
     * `after`, `ary`, `assign`, `at`, `before`, `bind`, `bindAll`, `bindKey`,
     * `callback`, `chain`, `chunk`, `commit`, `compact`, `concat`, `constant`,
     * `countBy`, `create`, `curry`, `debounce`, `defaults`, `defaultsDeep`,
     * `defer`, `delay`, `difference`, `drop`, `dropRight`, `dropRightWhile`,
     * `dropWhile`, `fill`, `filter`, `flatten`, `flattenDeep`, `flow`, `flowRight`,
     * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
     * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
     * `invoke`, `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`,
     * `matchesProperty`, `memoize`, `merge`, `method`, `methodOf`, `mixin`,
     * `modArgs`, `negate`, `omit`, `once`, `pairs`, `partial`, `partialRight`,
     * `partition`, `pick`, `plant`, `pluck`, `property`, `propertyOf`, `pull`,
     * `pullAt`, `push`, `range`, `rearg`, `reject`, `remove`, `rest`, `restParam`,
     * `reverse`, `set`, `shuffle`, `slice`, `sort`, `sortBy`, `sortByAll`,
     * `sortByOrder`, `splice`, `spread`, `take`, `takeRight`, `takeRightWhile`,
     * `takeWhile`, `tap`, `throttle`, `thru`, `times`, `toArray`, `toPlainObject`,
     * `transform`, `union`, `uniq`, `unshift`, `unzip`, `unzipWith`, `values`,
     * `valuesIn`, `where`, `without`, `wrap`, `xor`, `zip`, `zipObject`, `zipWith`
     *
     * The wrapper methods that are **not** chainable by default are:
     * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clone`, `cloneDeep`,
     * `deburr`, `endsWith`, `escape`, `escapeRegExp`, `every`, `find`, `findIndex`,
     * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `findWhere`, `first`,
     * `floor`, `get`, `gt`, `gte`, `has`, `identity`, `includes`, `indexOf`,
     * `inRange`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
     * `isEmpty`, `isEqual`, `isError`, `isFinite` `isFunction`, `isMatch`,
     * `isNative`, `isNaN`, `isNull`, `isNumber`, `isObject`, `isPlainObject`,
     * `isRegExp`, `isString`, `isUndefined`, `isTypedArray`, `join`, `kebabCase`,
     * `last`, `lastIndexOf`, `lt`, `lte`, `max`, `min`, `noConflict`, `noop`,
     * `now`, `pad`, `padLeft`, `padRight`, `parseInt`, `pop`, `random`, `reduce`,
     * `reduceRight`, `repeat`, `result`, `round`, `runInContext`, `shift`, `size`,
     * `snakeCase`, `some`, `sortedIndex`, `sortedLastIndex`, `startCase`,
     * `startsWith`, `sum`, `template`, `trim`, `trimLeft`, `trimRight`, `trunc`,
     * `unescape`, `uniqueId`, `value`, and `words`
     *
     * The wrapper method `sample` will return a wrapped value when `n` is provided,
     * otherwise an unwrapped value is returned.
     *
     * @name _
     * @constructor
     * @category Chain
     * @param {*} value The value to wrap in a `lodash` instance.
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var wrapped = _([1, 2, 3]);
     *
     * // returns an unwrapped value
     * wrapped.reduce(function(total, n) {
     *   return total + n;
     * });
     * // => 6
     *
     * // returns a wrapped value
     * var squares = wrapped.map(function(n) {
     *   return n * n;
     * });
     *
     * _.isArray(squares);
     * // => false
     *
     * _.isArray(squares.value());
     * // => true
     */
    function lodash(value) {
      if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
        if (value instanceof LodashWrapper) {
          return value;
        }
        if (hasOwnProperty.call(value, '__chain__') && hasOwnProperty.call(value, '__wrapped__')) {
          return wrapperClone(value);
        }
      }
      return new LodashWrapper(value);
    }

    /**
     * The function whose prototype all chaining wrappers inherit from.
     *
     * @private
     */
    function baseLodash() {
      // No operation performed.
    }

    /**
     * The base constructor for creating `lodash` wrapper objects.
     *
     * @private
     * @param {*} value The value to wrap.
     * @param {boolean} [chainAll] Enable chaining for all wrapper methods.
     * @param {Array} [actions=[]] Actions to peform to resolve the unwrapped value.
     */
    function LodashWrapper(value, chainAll, actions) {
      this.__wrapped__ = value;
      this.__actions__ = actions || [];
      this.__chain__ = !!chainAll;
    }

    /**
     * An object environment feature flags.
     *
     * @static
     * @memberOf _
     * @type Object
     */
    var support = lodash.support = {};

    /**
     * By default, the template delimiters used by lodash are like those in
     * embedded Ruby (ERB). Change the following template settings to use
     * alternative delimiters.
     *
     * @static
     * @memberOf _
     * @type Object
     */
    lodash.templateSettings = {

      /**
       * Used to detect `data` property values to be HTML-escaped.
       *
       * @memberOf _.templateSettings
       * @type RegExp
       */
      'escape': reEscape,

      /**
       * Used to detect code to be evaluated.
       *
       * @memberOf _.templateSettings
       * @type RegExp
       */
      'evaluate': reEvaluate,

      /**
       * Used to detect `data` property values to inject.
       *
       * @memberOf _.templateSettings
       * @type RegExp
       */
      'interpolate': reInterpolate,

      /**
       * Used to reference the data object in the template text.
       *
       * @memberOf _.templateSettings
       * @type string
       */
      'variable': '',

      /**
       * Used to import variables into the compiled template.
       *
       * @memberOf _.templateSettings
       * @type Object
       */
      'imports': {

        /**
         * A reference to the `lodash` function.
         *
         * @memberOf _.templateSettings.imports
         * @type Function
         */
        '_': lodash
      }
    };

    /*------------------------------------------------------------------------*/

    /**
     * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
     *
     * @private
     * @param {*} value The value to wrap.
     */
    function LazyWrapper(value) {
      this.__wrapped__ = value;
      this.__actions__ = [];
      this.__dir__ = 1;
      this.__filtered__ = false;
      this.__iteratees__ = [];
      this.__takeCount__ = POSITIVE_INFINITY;
      this.__views__ = [];
    }

    /**
     * Creates a clone of the lazy wrapper object.
     *
     * @private
     * @name clone
     * @memberOf LazyWrapper
     * @returns {Object} Returns the cloned `LazyWrapper` object.
     */
    function lazyClone() {
      var result = new LazyWrapper(this.__wrapped__);
      result.__actions__ = arrayCopy(this.__actions__);
      result.__dir__ = this.__dir__;
      result.__filtered__ = this.__filtered__;
      result.__iteratees__ = arrayCopy(this.__iteratees__);
      result.__takeCount__ = this.__takeCount__;
      result.__views__ = arrayCopy(this.__views__);
      return result;
    }

    /**
     * Reverses the direction of lazy iteration.
     *
     * @private
     * @name reverse
     * @memberOf LazyWrapper
     * @returns {Object} Returns the new reversed `LazyWrapper` object.
     */
    function lazyReverse() {
      if (this.__filtered__) {
        var result = new LazyWrapper(this);
        result.__dir__ = -1;
        result.__filtered__ = true;
      } else {
        result = this.clone();
        result.__dir__ *= -1;
      }
      return result;
    }

    /**
     * Extracts the unwrapped value from its lazy wrapper.
     *
     * @private
     * @name value
     * @memberOf LazyWrapper
     * @returns {*} Returns the unwrapped value.
     */
    function lazyValue() {
      var array = this.__wrapped__.value(),
          dir = this.__dir__,
          isArr = isArray(array),
          isRight = dir < 0,
          arrLength = isArr ? array.length : 0,
          view = getView(0, arrLength, this.__views__),
          start = view.start,
          end = view.end,
          length = end - start,
          index = isRight ? end : (start - 1),
          iteratees = this.__iteratees__,
          iterLength = iteratees.length,
          resIndex = 0,
          takeCount = nativeMin(length, this.__takeCount__);

      if (!isArr || arrLength < LARGE_ARRAY_SIZE || (arrLength == length && takeCount == length)) {
        return baseWrapperValue((isRight && isArr) ? array.reverse() : array, this.__actions__);
      }
      var result = [];

      outer:
      while (length-- && resIndex < takeCount) {
        index += dir;

        var iterIndex = -1,
            value = array[index];

        while (++iterIndex < iterLength) {
          var data = iteratees[iterIndex],
              iteratee = data.iteratee,
              type = data.type,
              computed = iteratee(value);

          if (type == LAZY_MAP_FLAG) {
            value = computed;
          } else if (!computed) {
            if (type == LAZY_FILTER_FLAG) {
              continue outer;
            } else {
              break outer;
            }
          }
        }
        result[resIndex++] = value;
      }
      return result;
    }

    /*------------------------------------------------------------------------*/

    /**
     * Creates a cache object to store key/value pairs.
     *
     * @private
     * @static
     * @name Cache
     * @memberOf _.memoize
     */
    function MapCache() {
      this.__data__ = {};
    }

    /**
     * Removes `key` and its value from the cache.
     *
     * @private
     * @name delete
     * @memberOf _.memoize.Cache
     * @param {string} key The key of the value to remove.
     * @returns {boolean} Returns `true` if the entry was removed successfully, else `false`.
     */
    function mapDelete(key) {
      return this.has(key) && delete this.__data__[key];
    }

    /**
     * Gets the cached value for `key`.
     *
     * @private
     * @name get
     * @memberOf _.memoize.Cache
     * @param {string} key The key of the value to get.
     * @returns {*} Returns the cached value.
     */
    function mapGet(key) {
      return key == '__proto__' ? undefined : this.__data__[key];
    }

    /**
     * Checks if a cached value for `key` exists.
     *
     * @private
     * @name has
     * @memberOf _.memoize.Cache
     * @param {string} key The key of the entry to check.
     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
     */
    function mapHas(key) {
      return key != '__proto__' && hasOwnProperty.call(this.__data__, key);
    }

    /**
     * Sets `value` to `key` of the cache.
     *
     * @private
     * @name set
     * @memberOf _.memoize.Cache
     * @param {string} key The key of the value to cache.
     * @param {*} value The value to cache.
     * @returns {Object} Returns the cache object.
     */
    function mapSet(key, value) {
      if (key != '__proto__') {
        this.__data__[key] = value;
      }
      return this;
    }

    /*------------------------------------------------------------------------*/

    /**
     *
     * Creates a cache object to store unique values.
     *
     * @private
     * @param {Array} [values] The values to cache.
     */
    function SetCache(values) {
      var length = values ? values.length : 0;

      this.data = { 'hash': nativeCreate(null), 'set': new Set };
      while (length--) {
        this.push(values[length]);
      }
    }

    /**
     * Checks if `value` is in `cache` mimicking the return signature of
     * `_.indexOf` by returning `0` if the value is found, else `-1`.
     *
     * @private
     * @param {Object} cache The cache to search.
     * @param {*} value The value to search for.
     * @returns {number} Returns `0` if `value` is found, else `-1`.
     */
    function cacheIndexOf(cache, value) {
      var data = cache.data,
          result = (typeof value == 'string' || isObject(value)) ? data.set.has(value) : data.hash[value];

      return result ? 0 : -1;
    }

    /**
     * Adds `value` to the cache.
     *
     * @private
     * @name push
     * @memberOf SetCache
     * @param {*} value The value to cache.
     */
    function cachePush(value) {
      var data = this.data;
      if (typeof value == 'string' || isObject(value)) {
        data.set.add(value);
      } else {
        data.hash[value] = true;
      }
    }

    /*------------------------------------------------------------------------*/

    /**
     * Creates a new array joining `array` with `other`.
     *
     * @private
     * @param {Array} array The array to join.
     * @param {Array} other The other array to join.
     * @returns {Array} Returns the new concatenated array.
     */
    function arrayConcat(array, other) {
      var index = -1,
          length = array.length,
          othIndex = -1,
          othLength = other.length,
          result = Array(length + othLength);

      while (++index < length) {
        result[index] = array[index];
      }
      while (++othIndex < othLength) {
        result[index++] = other[othIndex];
      }
      return result;
    }

    /**
     * Copies the values of `source` to `array`.
     *
     * @private
     * @param {Array} source The array to copy values from.
     * @param {Array} [array=[]] The array to copy values to.
     * @returns {Array} Returns `array`.
     */
    function arrayCopy(source, array) {
      var index = -1,
          length = source.length;

      array || (array = Array(length));
      while (++index < length) {
        array[index] = source[index];
      }
      return array;
    }

    /**
     * A specialized version of `_.forEach` for arrays without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Array} Returns `array`.
     */
    function arrayEach(array, iteratee) {
      var index = -1,
          length = array.length;

      while (++index < length) {
        if (iteratee(array[index], index, array) === false) {
          break;
        }
      }
      return array;
    }

    /**
     * A specialized version of `_.forEachRight` for arrays without support for
     * callback shorthands and `this` binding.
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Array} Returns `array`.
     */
    function arrayEachRight(array, iteratee) {
      var length = array.length;

      while (length--) {
        if (iteratee(array[length], length, array) === false) {
          break;
        }
      }
      return array;
    }

    /**
     * A specialized version of `_.every` for arrays without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {boolean} Returns `true` if all elements pass the predicate check,
     *  else `false`.
     */
    function arrayEvery(array, predicate) {
      var index = -1,
          length = array.length;

      while (++index < length) {
        if (!predicate(array[index], index, array)) {
          return false;
        }
      }
      return true;
    }

    /**
     * A specialized version of `baseExtremum` for arrays which invokes `iteratee`
     * with one argument: (value).
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {Function} comparator The function used to compare values.
     * @param {*} exValue The initial extremum value.
     * @returns {*} Returns the extremum value.
     */
    function arrayExtremum(array, iteratee, comparator, exValue) {
      var index = -1,
          length = array.length,
          computed = exValue,
          result = computed;

      while (++index < length) {
        var value = array[index],
            current = +iteratee(value);

        if (comparator(current, computed)) {
          computed = current;
          result = value;
        }
      }
      return result;
    }

    /**
     * A specialized version of `_.filter` for arrays without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {Array} Returns the new filtered array.
     */
    function arrayFilter(array, predicate) {
      var index = -1,
          length = array.length,
          resIndex = -1,
          result = [];

      while (++index < length) {
        var value = array[index];
        if (predicate(value, index, array)) {
          result[++resIndex] = value;
        }
      }
      return result;
    }

    /**
     * A specialized version of `_.map` for arrays without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Array} Returns the new mapped array.
     */
    function arrayMap(array, iteratee) {
      var index = -1,
          length = array.length,
          result = Array(length);

      while (++index < length) {
        result[index] = iteratee(array[index], index, array);
      }
      return result;
    }

    /**
     * Appends the elements of `values` to `array`.
     *
     * @private
     * @param {Array} array The array to modify.
     * @param {Array} values The values to append.
     * @returns {Array} Returns `array`.
     */
    function arrayPush(array, values) {
      var index = -1,
          length = values.length,
          offset = array.length;

      while (++index < length) {
        array[offset + index] = values[index];
      }
      return array;
    }

    /**
     * A specialized version of `_.reduce` for arrays without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {*} [accumulator] The initial value.
     * @param {boolean} [initFromArray] Specify using the first element of `array`
     *  as the initial value.
     * @returns {*} Returns the accumulated value.
     */
    function arrayReduce(array, iteratee, accumulator, initFromArray) {
      var index = -1,
          length = array.length;

      if (initFromArray && length) {
        accumulator = array[++index];
      }
      while (++index < length) {
        accumulator = iteratee(accumulator, array[index], index, array);
      }
      return accumulator;
    }

    /**
     * A specialized version of `_.reduceRight` for arrays without support for
     * callback shorthands and `this` binding.
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {*} [accumulator] The initial value.
     * @param {boolean} [initFromArray] Specify using the last element of `array`
     *  as the initial value.
     * @returns {*} Returns the accumulated value.
     */
    function arrayReduceRight(array, iteratee, accumulator, initFromArray) {
      var length = array.length;
      if (initFromArray && length) {
        accumulator = array[--length];
      }
      while (length--) {
        accumulator = iteratee(accumulator, array[length], length, array);
      }
      return accumulator;
    }

    /**
     * A specialized version of `_.some` for arrays without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {boolean} Returns `true` if any element passes the predicate check,
     *  else `false`.
     */
    function arraySome(array, predicate) {
      var index = -1,
          length = array.length;

      while (++index < length) {
        if (predicate(array[index], index, array)) {
          return true;
        }
      }
      return false;
    }

    /**
     * A specialized version of `_.sum` for arrays without support for callback
     * shorthands and `this` binding..
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {number} Returns the sum.
     */
    function arraySum(array, iteratee) {
      var length = array.length,
          result = 0;

      while (length--) {
        result += +iteratee(array[length]) || 0;
      }
      return result;
    }

    /**
     * Used by `_.defaults` to customize its `_.assign` use.
     *
     * @private
     * @param {*} objectValue The destination object property value.
     * @param {*} sourceValue The source object property value.
     * @returns {*} Returns the value to assign to the destination object.
     */
    function assignDefaults(objectValue, sourceValue) {
      return objectValue === undefined ? sourceValue : objectValue;
    }

    /**
     * Used by `_.template` to customize its `_.assign` use.
     *
     * **Note:** This function is like `assignDefaults` except that it ignores
     * inherited property values when checking if a property is `undefined`.
     *
     * @private
     * @param {*} objectValue The destination object property value.
     * @param {*} sourceValue The source object property value.
     * @param {string} key The key associated with the object and source values.
     * @param {Object} object The destination object.
     * @returns {*} Returns the value to assign to the destination object.
     */
    function assignOwnDefaults(objectValue, sourceValue, key, object) {
      return (objectValue === undefined || !hasOwnProperty.call(object, key))
        ? sourceValue
        : objectValue;
    }

    /**
     * A specialized version of `_.assign` for customizing assigned values without
     * support for argument juggling, multiple sources, and `this` binding `customizer`
     * functions.
     *
     * @private
     * @param {Object} object The destination object.
     * @param {Object} source The source object.
     * @param {Function} customizer The function to customize assigned values.
     * @returns {Object} Returns `object`.
     */
    function assignWith(object, source, customizer) {
      var index = -1,
          props = keys(source),
          length = props.length;

      while (++index < length) {
        var key = props[index],
            value = object[key],
            result = customizer(value, source[key], key, object, source);

        if ((result === result ? (result !== value) : (value === value)) ||
            (value === undefined && !(key in object))) {
          object[key] = result;
        }
      }
      return object;
    }

    /**
     * The base implementation of `_.assign` without support for argument juggling,
     * multiple sources, and `customizer` functions.
     *
     * @private
     * @param {Object} object The destination object.
     * @param {Object} source The source object.
     * @returns {Object} Returns `object`.
     */
    function baseAssign(object, source) {
      return source == null
        ? object
        : baseCopy(source, keys(source), object);
    }

    /**
     * The base implementation of `_.at` without support for string collections
     * and individual key arguments.
     *
     * @private
     * @param {Array|Object} collection The collection to iterate over.
     * @param {number[]|string[]} props The property names or indexes of elements to pick.
     * @returns {Array} Returns the new array of picked elements.
     */
    function baseAt(collection, props) {
      var index = -1,
          isNil = collection == null,
          isArr = !isNil && isArrayLike(collection),
          length = isArr ? collection.length : 0,
          propsLength = props.length,
          result = Array(propsLength);

      while(++index < propsLength) {
        var key = props[index];
        if (isArr) {
          result[index] = isIndex(key, length) ? collection[key] : undefined;
        } else {
          result[index] = isNil ? undefined : collection[key];
        }
      }
      return result;
    }

    /**
     * Copies properties of `source` to `object`.
     *
     * @private
     * @param {Object} source The object to copy properties from.
     * @param {Array} props The property names to copy.
     * @param {Object} [object={}] The object to copy properties to.
     * @returns {Object} Returns `object`.
     */
    function baseCopy(source, props, object) {
      object || (object = {});

      var index = -1,
          length = props.length;

      while (++index < length) {
        var key = props[index];
        object[key] = source[key];
      }
      return object;
    }

    /**
     * The base implementation of `_.callback` which supports specifying the
     * number of arguments to provide to `func`.
     *
     * @private
     * @param {*} [func=_.identity] The value to convert to a callback.
     * @param {*} [thisArg] The `this` binding of `func`.
     * @param {number} [argCount] The number of arguments to provide to `func`.
     * @returns {Function} Returns the callback.
     */
    function baseCallback(func, thisArg, argCount) {
      var type = typeof func;
      if (type == 'function') {
        return thisArg === undefined
          ? func
          : bindCallback(func, thisArg, argCount);
      }
      if (func == null) {
        return identity;
      }
      if (type == 'object') {
        return baseMatches(func);
      }
      return thisArg === undefined
        ? property(func)
        : baseMatchesProperty(func, thisArg);
    }

    /**
     * The base implementation of `_.clone` without support for argument juggling
     * and `this` binding `customizer` functions.
     *
     * @private
     * @param {*} value The value to clone.
     * @param {boolean} [isDeep] Specify a deep clone.
     * @param {Function} [customizer] The function to customize cloning values.
     * @param {string} [key] The key of `value`.
     * @param {Object} [object] The object `value` belongs to.
     * @param {Array} [stackA=[]] Tracks traversed source objects.
     * @param {Array} [stackB=[]] Associates clones with source counterparts.
     * @returns {*} Returns the cloned value.
     */
    function baseClone(value, isDeep, customizer, key, object, stackA, stackB) {
      var result;
      if (customizer) {
        result = object ? customizer(value, key, object) : customizer(value);
      }
      if (result !== undefined) {
        return result;
      }
      if (!isObject(value)) {
        return value;
      }
      var isArr = isArray(value);
      if (isArr) {
        result = initCloneArray(value);
        if (!isDeep) {
          return arrayCopy(value, result);
        }
      } else {
        var tag = objToString.call(value),
            isFunc = tag == funcTag;

        if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
          result = initCloneObject(isFunc ? {} : value);
          if (!isDeep) {
            return baseAssign(result, value);
          }
        } else {
          return cloneableTags[tag]
            ? initCloneByTag(value, tag, isDeep)
            : (object ? value : {});
        }
      }
      // Check for circular references and return its corresponding clone.
      stackA || (stackA = []);
      stackB || (stackB = []);

      var length = stackA.length;
      while (length--) {
        if (stackA[length] == value) {
          return stackB[length];
        }
      }
      // Add the source value to the stack of traversed objects and associate it with its clone.
      stackA.push(value);
      stackB.push(result);

      // Recursively populate clone (susceptible to call stack limits).
      (isArr ? arrayEach : baseForOwn)(value, function(subValue, key) {
        result[key] = baseClone(subValue, isDeep, customizer, key, value, stackA, stackB);
      });
      return result;
    }

    /**
     * The base implementation of `_.create` without support for assigning
     * properties to the created object.
     *
     * @private
     * @param {Object} prototype The object to inherit from.
     * @returns {Object} Returns the new object.
     */
    var baseCreate = (function() {
      function object() {}
      return function(prototype) {
        if (isObject(prototype)) {
          object.prototype = prototype;
          var result = new object;
          object.prototype = undefined;
        }
        return result || {};
      };
    }());

    /**
     * The base implementation of `_.delay` and `_.defer` which accepts an index
     * of where to slice the arguments to provide to `func`.
     *
     * @private
     * @param {Function} func The function to delay.
     * @param {number} wait The number of milliseconds to delay invocation.
     * @param {Object} args The arguments provide to `func`.
     * @returns {number} Returns the timer id.
     */
    function baseDelay(func, wait, args) {
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      return setTimeout(function() { func.apply(undefined, args); }, wait);
    }

    /**
     * The base implementation of `_.difference` which accepts a single array
     * of values to exclude.
     *
     * @private
     * @param {Array} array The array to inspect.
     * @param {Array} values The values to exclude.
     * @returns {Array} Returns the new array of filtered values.
     */
    function baseDifference(array, values) {
      var length = array ? array.length : 0,
          result = [];

      if (!length) {
        return result;
      }
      var index = -1,
          indexOf = getIndexOf(),
          isCommon = indexOf == baseIndexOf,
          cache = (isCommon && values.length >= LARGE_ARRAY_SIZE) ? createCache(values) : null,
          valuesLength = values.length;

      if (cache) {
        indexOf = cacheIndexOf;
        isCommon = false;
        values = cache;
      }
      outer:
      while (++index < length) {
        var value = array[index];

        if (isCommon && value === value) {
          var valuesIndex = valuesLength;
          while (valuesIndex--) {
            if (values[valuesIndex] === value) {
              continue outer;
            }
          }
          result.push(value);
        }
        else if (indexOf(values, value, 0) < 0) {
          result.push(value);
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.forEach` without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Array|Object|string} Returns `collection`.
     */
    var baseEach = createBaseEach(baseForOwn);

    /**
     * The base implementation of `_.forEachRight` without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Array|Object|string} Returns `collection`.
     */
    var baseEachRight = createBaseEach(baseForOwnRight, true);

    /**
     * The base implementation of `_.every` without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {boolean} Returns `true` if all elements pass the predicate check,
     *  else `false`
     */
    function baseEvery(collection, predicate) {
      var result = true;
      baseEach(collection, function(value, index, collection) {
        result = !!predicate(value, index, collection);
        return result;
      });
      return result;
    }

    /**
     * Gets the extremum value of `collection` invoking `iteratee` for each value
     * in `collection` to generate the criterion by which the value is ranked.
     * The `iteratee` is invoked with three arguments: (value, index|key, collection).
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {Function} comparator The function used to compare values.
     * @param {*} exValue The initial extremum value.
     * @returns {*} Returns the extremum value.
     */
    function baseExtremum(collection, iteratee, comparator, exValue) {
      var computed = exValue,
          result = computed;

      baseEach(collection, function(value, index, collection) {
        var current = +iteratee(value, index, collection);
        if (comparator(current, computed) || (current === exValue && current === result)) {
          computed = current;
          result = value;
        }
      });
      return result;
    }

    /**
     * The base implementation of `_.fill` without an iteratee call guard.
     *
     * @private
     * @param {Array} array The array to fill.
     * @param {*} value The value to fill `array` with.
     * @param {number} [start=0] The start position.
     * @param {number} [end=array.length] The end position.
     * @returns {Array} Returns `array`.
     */
    function baseFill(array, value, start, end) {
      var length = array.length;

      start = start == null ? 0 : (+start || 0);
      if (start < 0) {
        start = -start > length ? 0 : (length + start);
      }
      end = (end === undefined || end > length) ? length : (+end || 0);
      if (end < 0) {
        end += length;
      }
      length = start > end ? 0 : (end >>> 0);
      start >>>= 0;

      while (start < length) {
        array[start++] = value;
      }
      return array;
    }

    /**
     * The base implementation of `_.filter` without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {Array} Returns the new filtered array.
     */
    function baseFilter(collection, predicate) {
      var result = [];
      baseEach(collection, function(value, index, collection) {
        if (predicate(value, index, collection)) {
          result.push(value);
        }
      });
      return result;
    }

    /**
     * The base implementation of `_.find`, `_.findLast`, `_.findKey`, and `_.findLastKey`,
     * without support for callback shorthands and `this` binding, which iterates
     * over `collection` using the provided `eachFunc`.
     *
     * @private
     * @param {Array|Object|string} collection The collection to search.
     * @param {Function} predicate The function invoked per iteration.
     * @param {Function} eachFunc The function to iterate over `collection`.
     * @param {boolean} [retKey] Specify returning the key of the found element
     *  instead of the element itself.
     * @returns {*} Returns the found element or its key, else `undefined`.
     */
    function baseFind(collection, predicate, eachFunc, retKey) {
      var result;
      eachFunc(collection, function(value, key, collection) {
        if (predicate(value, key, collection)) {
          result = retKey ? key : value;
          return false;
        }
      });
      return result;
    }

    /**
     * The base implementation of `_.flatten` with added support for restricting
     * flattening and specifying the start index.
     *
     * @private
     * @param {Array} array The array to flatten.
     * @param {boolean} [isDeep] Specify a deep flatten.
     * @param {boolean} [isStrict] Restrict flattening to arrays-like objects.
     * @param {Array} [result=[]] The initial result value.
     * @returns {Array} Returns the new flattened array.
     */
    function baseFlatten(array, isDeep, isStrict, result) {
      result || (result = []);

      var index = -1,
          length = array.length;

      while (++index < length) {
        var value = array[index];
        if (isObjectLike(value) && isArrayLike(value) &&
            (isStrict || isArray(value) || isArguments(value))) {
          if (isDeep) {
            // Recursively flatten arrays (susceptible to call stack limits).
            baseFlatten(value, isDeep, isStrict, result);
          } else {
            arrayPush(result, value);
          }
        } else if (!isStrict) {
          result[result.length] = value;
        }
      }
      return result;
    }

    /**
     * The base implementation of `baseForIn` and `baseForOwn` which iterates
     * over `object` properties returned by `keysFunc` invoking `iteratee` for
     * each property. Iteratee functions may exit iteration early by explicitly
     * returning `false`.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {Function} keysFunc The function to get the keys of `object`.
     * @returns {Object} Returns `object`.
     */
    var baseFor = createBaseFor();

    /**
     * This function is like `baseFor` except that it iterates over properties
     * in the opposite order.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {Function} keysFunc The function to get the keys of `object`.
     * @returns {Object} Returns `object`.
     */
    var baseForRight = createBaseFor(true);

    /**
     * The base implementation of `_.forIn` without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Object} Returns `object`.
     */
    function baseForIn(object, iteratee) {
      return baseFor(object, iteratee, keysIn);
    }

    /**
     * The base implementation of `_.forOwn` without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Object} Returns `object`.
     */
    function baseForOwn(object, iteratee) {
      return baseFor(object, iteratee, keys);
    }

    /**
     * The base implementation of `_.forOwnRight` without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Object} Returns `object`.
     */
    function baseForOwnRight(object, iteratee) {
      return baseForRight(object, iteratee, keys);
    }

    /**
     * The base implementation of `_.functions` which creates an array of
     * `object` function property names filtered from those provided.
     *
     * @private
     * @param {Object} object The object to inspect.
     * @param {Array} props The property names to filter.
     * @returns {Array} Returns the new array of filtered property names.
     */
    function baseFunctions(object, props) {
      var index = -1,
          length = props.length,
          resIndex = -1,
          result = [];

      while (++index < length) {
        var key = props[index];
        if (isFunction(object[key])) {
          result[++resIndex] = key;
        }
      }
      return result;
    }

    /**
     * The base implementation of `get` without support for string paths
     * and default values.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {Array} path The path of the property to get.
     * @param {string} [pathKey] The key representation of path.
     * @returns {*} Returns the resolved value.
     */
    function baseGet(object, path, pathKey) {
      if (object == null) {
        return;
      }
      if (pathKey !== undefined && pathKey in toObject(object)) {
        path = [pathKey];
      }
      var index = 0,
          length = path.length;

      while (object != null && index < length) {
        object = object[path[index++]];
      }
      return (index && index == length) ? object : undefined;
    }

    /**
     * The base implementation of `_.isEqual` without support for `this` binding
     * `customizer` functions.
     *
     * @private
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @param {Function} [customizer] The function to customize comparing values.
     * @param {boolean} [isLoose] Specify performing partial comparisons.
     * @param {Array} [stackA] Tracks traversed `value` objects.
     * @param {Array} [stackB] Tracks traversed `other` objects.
     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
     */
    function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) {
      if (value === other) {
        return true;
      }
      if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
        return value !== value && other !== other;
      }
      return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB);
    }

    /**
     * A specialized version of `baseIsEqual` for arrays and objects which performs
     * deep comparisons and tracks traversed objects enabling objects with circular
     * references to be compared.
     *
     * @private
     * @param {Object} object The object to compare.
     * @param {Object} other The other object to compare.
     * @param {Function} equalFunc The function to determine equivalents of values.
     * @param {Function} [customizer] The function to customize comparing objects.
     * @param {boolean} [isLoose] Specify performing partial comparisons.
     * @param {Array} [stackA=[]] Tracks traversed `value` objects.
     * @param {Array} [stackB=[]] Tracks traversed `other` objects.
     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
     */
    function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
      var objIsArr = isArray(object),
          othIsArr = isArray(other),
          objTag = arrayTag,
          othTag = arrayTag;

      if (!objIsArr) {
        objTag = objToString.call(object);
        if (objTag == argsTag) {
          objTag = objectTag;
        } else if (objTag != objectTag) {
          objIsArr = isTypedArray(object);
        }
      }
      if (!othIsArr) {
        othTag = objToString.call(other);
        if (othTag == argsTag) {
          othTag = objectTag;
        } else if (othTag != objectTag) {
          othIsArr = isTypedArray(other);
        }
      }
      var objIsObj = objTag == objectTag,
          othIsObj = othTag == objectTag,
          isSameTag = objTag == othTag;

      if (isSameTag && !(objIsArr || objIsObj)) {
        return equalByTag(object, other, objTag);
      }
      if (!isLoose) {
        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
            othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');

        if (objIsWrapped || othIsWrapped) {
          return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, isLoose, stackA, stackB);
        }
      }
      if (!isSameTag) {
        return false;
      }
      // Assume cyclic values are equal.
      // For more information on detecting circular references see https://es5.github.io/#JO.
      stackA || (stackA = []);
      stackB || (stackB = []);

      var length = stackA.length;
      while (length--) {
        if (stackA[length] == object) {
          return stackB[length] == other;
        }
      }
      // Add `object` and `other` to the stack of traversed objects.
      stackA.push(object);
      stackB.push(other);

      var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB);

      stackA.pop();
      stackB.pop();

      return result;
    }

    /**
     * The base implementation of `_.isMatch` without support for callback
     * shorthands and `this` binding.
     *
     * @private
     * @param {Object} object The object to inspect.
     * @param {Array} matchData The propery names, values, and compare flags to match.
     * @param {Function} [customizer] The function to customize comparing objects.
     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
     */
    function baseIsMatch(object, matchData, customizer) {
      var index = matchData.length,
          length = index,
          noCustomizer = !customizer;

      if (object == null) {
        return !length;
      }
      object = toObject(object);
      while (index--) {
        var data = matchData[index];
        if ((noCustomizer && data[2])
              ? data[1] !== object[data[0]]
              : !(data[0] in object)
            ) {
          return false;
        }
      }
      while (++index < length) {
        data = matchData[index];
        var key = data[0],
            objValue = object[key],
            srcValue = data[1];

        if (noCustomizer && data[2]) {
          if (objValue === undefined && !(key in object)) {
            return false;
          }
        } else {
          var result = customizer ? customizer(objValue, srcValue, key) : undefined;
          if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, true) : result)) {
            return false;
          }
        }
      }
      return true;
    }

    /**
     * The base implementation of `_.map` without support for callback shorthands
     * and `this` binding.
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Array} Returns the new mapped array.
     */
    function baseMap(collection, iteratee) {
      var index = -1,
          result = isArrayLike(collection) ? Array(collection.length) : [];

      baseEach(collection, function(value, key, collection) {
        result[++index] = iteratee(value, key, collection);
      });
      return result;
    }

    /**
     * The base implementation of `_.matches` which does not clone `source`.
     *
     * @private
     * @param {Object} source The object of property values to match.
     * @returns {Function} Returns the new function.
     */
    function baseMatches(source) {
      var matchData = getMatchData(source);
      if (matchData.length == 1 && matchData[0][2]) {
        var key = matchData[0][0],
            value = matchData[0][1];

        return function(object) {
          if (object == null) {
            return false;
          }
          return object[key] === value && (value !== undefined || (key in toObject(object)));
        };
      }
      return function(object) {
        return baseIsMatch(object, matchData);
      };
    }

    /**
     * The base implementation of `_.matchesProperty` which does not clone `srcValue`.
     *
     * @private
     * @param {string} path The path of the property to get.
     * @param {*} srcValue The value to compare.
     * @returns {Function} Returns the new function.
     */
    function baseMatchesProperty(path, srcValue) {
      var isArr = isArray(path),
          isCommon = isKey(path) && isStrictComparable(srcValue),
          pathKey = (path + '');

      path = toPath(path);
      return function(object) {
        if (object == null) {
          return false;
        }
        var key = pathKey;
        object = toObject(object);
        if ((isArr || !isCommon) && !(key in object)) {
          object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
          if (object == null) {
            return false;
          }
          key = last(path);
          object = toObject(object);
        }
        return object[key] === srcValue
          ? (srcValue !== undefined || (key in object))
          : baseIsEqual(srcValue, object[key], undefined, true);
      };
    }

    /**
     * The base implementation of `_.merge` without support for argument juggling,
     * multiple sources, and `this` binding `customizer` functions.
     *
     * @private
     * @param {Object} object The destination object.
     * @param {Object} source The source object.
     * @param {Function} [customizer] The function to customize merged values.
     * @param {Array} [stackA=[]] Tracks traversed source objects.
     * @param {Array} [stackB=[]] Associates values with source counterparts.
     * @returns {Object} Returns `object`.
     */
    function baseMerge(object, source, customizer, stackA, stackB) {
      if (!isObject(object)) {
        return object;
      }
      var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)),
          props = isSrcArr ? undefined : keys(source);

      arrayEach(props || source, function(srcValue, key) {
        if (props) {
          key = srcValue;
          srcValue = source[key];
        }
        if (isObjectLike(srcValue)) {
          stackA || (stackA = []);
          stackB || (stackB = []);
          baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB);
        }
        else {
          var value = object[key],
              result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
              isCommon = result === undefined;

          if (isCommon) {
            result = srcValue;
          }
          if ((result !== undefined || (isSrcArr && !(key in object))) &&
              (isCommon || (result === result ? (result !== value) : (value === value)))) {
            object[key] = result;
          }
        }
      });
      return object;
    }

    /**
     * A specialized version of `baseMerge` for arrays and objects which performs
     * deep merges and tracks traversed objects enabling objects with circular
     * references to be merged.
     *
     * @private
     * @param {Object} object The destination object.
     * @param {Object} source The source object.
     * @param {string} key The key of the value to merge.
     * @param {Function} mergeFunc The function to merge values.
     * @param {Function} [customizer] The function to customize merged values.
     * @param {Array} [stackA=[]] Tracks traversed source objects.
     * @param {Array} [stackB=[]] Associates values with source counterparts.
     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
     */
    function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) {
      var length = stackA.length,
          srcValue = source[key];

      while (length--) {
        if (stackA[length] == srcValue) {
          object[key] = stackB[length];
          return;
        }
      }
      var value = object[key],
          result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
          isCommon = result === undefined;

      if (isCommon) {
        result = srcValue;
        if (isArrayLike(srcValue) && (isArray(srcValue) || isTypedArray(srcValue))) {
          result = isArray(value)
            ? value
            : (isArrayLike(value) ? arrayCopy(value) : []);
        }
        else if (isPlainObject(srcValue) || isArguments(srcValue)) {
          result = isArguments(value)
            ? toPlainObject(value)
            : (isPlainObject(value) ? value : {});
        }
        else {
          isCommon = false;
        }
      }
      // Add the source value to the stack of traversed objects and associate
      // it with its merged value.
      stackA.push(srcValue);
      stackB.push(result);

      if (isCommon) {
        // Recursively merge objects and arrays (susceptible to call stack limits).
        object[key] = mergeFunc(result, srcValue, customizer, stackA, stackB);
      } else if (result === result ? (result !== value) : (value === value)) {
        object[key] = result;
      }
    }

    /**
     * The base implementation of `_.property` without support for deep paths.
     *
     * @private
     * @param {string} key The key of the property to get.
     * @returns {Function} Returns the new function.
     */
    function baseProperty(key) {
      return function(object) {
        return object == null ? undefined : object[key];
      };
    }

    /**
     * A specialized version of `baseProperty` which supports deep paths.
     *
     * @private
     * @param {Array|string} path The path of the property to get.
     * @returns {Function} Returns the new function.
     */
    function basePropertyDeep(path) {
      var pathKey = (path + '');
      path = toPath(path);
      return function(object) {
        return baseGet(object, path, pathKey);
      };
    }

    /**
     * The base implementation of `_.pullAt` without support for individual
     * index arguments and capturing the removed elements.
     *
     * @private
     * @param {Array} array The array to modify.
     * @param {number[]} indexes The indexes of elements to remove.
     * @returns {Array} Returns `array`.
     */
    function basePullAt(array, indexes) {
      var length = array ? indexes.length : 0;
      while (length--) {
        var index = indexes[length];
        if (index != previous && isIndex(index)) {
          var previous = index;
          splice.call(array, index, 1);
        }
      }
      return array;
    }

    /**
     * The base implementation of `_.random` without support for argument juggling
     * and returning floating-point numbers.
     *
     * @private
     * @param {number} min The minimum possible value.
     * @param {number} max The maximum possible value.
     * @returns {number} Returns the random number.
     */
    function baseRandom(min, max) {
      return min + nativeFloor(nativeRandom() * (max - min + 1));
    }

    /**
     * The base implementation of `_.reduce` and `_.reduceRight` without support
     * for callback shorthands and `this` binding, which iterates over `collection`
     * using the provided `eachFunc`.
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {*} accumulator The initial value.
     * @param {boolean} initFromCollection Specify using the first or last element
     *  of `collection` as the initial value.
     * @param {Function} eachFunc The function to iterate over `collection`.
     * @returns {*} Returns the accumulated value.
     */
    function baseReduce(collection, iteratee, accumulator, initFromCollection, eachFunc) {
      eachFunc(collection, function(value, index, collection) {
        accumulator = initFromCollection
          ? (initFromCollection = false, value)
          : iteratee(accumulator, value, index, collection);
      });
      return accumulator;
    }

    /**
     * The base implementation of `setData` without support for hot loop detection.
     *
     * @private
     * @param {Function} func The function to associate metadata with.
     * @param {*} data The metadata.
     * @returns {Function} Returns `func`.
     */
    var baseSetData = !metaMap ? identity : function(func, data) {
      metaMap.set(func, data);
      return func;
    };

    /**
     * The base implementation of `_.slice` without an iteratee call guard.
     *
     * @private
     * @param {Array} array The array to slice.
     * @param {number} [start=0] The start position.
     * @param {number} [end=array.length] The end position.
     * @returns {Array} Returns the slice of `array`.
     */
    function baseSlice(array, start, end) {
      var index = -1,
          length = array.length;

      start = start == null ? 0 : (+start || 0);
      if (start < 0) {
        start = -start > length ? 0 : (length + start);
      }
      end = (end === undefined || end > length) ? length : (+end || 0);
      if (end < 0) {
        end += length;
      }
      length = start > end ? 0 : ((end - start) >>> 0);
      start >>>= 0;

      var result = Array(length);
      while (++index < length) {
        result[index] = array[index + start];
      }
      return result;
    }

    /**
     * The base implementation of `_.some` without support for callback shorthands
     * and `this` binding.
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {boolean} Returns `true` if any element passes the predicate check,
     *  else `false`.
     */
    function baseSome(collection, predicate) {
      var result;

      baseEach(collection, function(value, index, collection) {
        result = predicate(value, index, collection);
        return !result;
      });
      return !!result;
    }

    /**
     * The base implementation of `_.sortBy` which uses `comparer` to define
     * the sort order of `array` and replaces criteria objects with their
     * corresponding values.
     *
     * @private
     * @param {Array} array The array to sort.
     * @param {Function} comparer The function to define sort order.
     * @returns {Array} Returns `array`.
     */
    function baseSortBy(array, comparer) {
      var length = array.length;

      array.sort(comparer);
      while (length--) {
        array[length] = array[length].value;
      }
      return array;
    }

    /**
     * The base implementation of `_.sortByOrder` without param guards.
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
     * @param {boolean[]} orders The sort orders of `iteratees`.
     * @returns {Array} Returns the new sorted array.
     */
    function baseSortByOrder(collection, iteratees, orders) {
      var callback = getCallback(),
          index = -1;

      iteratees = arrayMap(iteratees, function(iteratee) { return callback(iteratee); });

      var result = baseMap(collection, function(value) {
        var criteria = arrayMap(iteratees, function(iteratee) { return iteratee(value); });
        return { 'criteria': criteria, 'index': ++index, 'value': value };
      });

      return baseSortBy(result, function(object, other) {
        return compareMultiple(object, other, orders);
      });
    }

    /**
     * The base implementation of `_.sum` without support for callback shorthands
     * and `this` binding.
     *
     * @private
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {number} Returns the sum.
     */
    function baseSum(collection, iteratee) {
      var result = 0;
      baseEach(collection, function(value, index, collection) {
        result += +iteratee(value, index, collection) || 0;
      });
      return result;
    }

    /**
     * The base implementation of `_.uniq` without support for callback shorthands
     * and `this` binding.
     *
     * @private
     * @param {Array} array The array to inspect.
     * @param {Function} [iteratee] The function invoked per iteration.
     * @returns {Array} Returns the new duplicate-value-free array.
     */
    function baseUniq(array, iteratee) {
      var index = -1,
          indexOf = getIndexOf(),
          length = array.length,
          isCommon = indexOf == baseIndexOf,
          isLarge = isCommon && length >= LARGE_ARRAY_SIZE,
          seen = isLarge ? createCache() : null,
          result = [];

      if (seen) {
        indexOf = cacheIndexOf;
        isCommon = false;
      } else {
        isLarge = false;
        seen = iteratee ? [] : result;
      }
      outer:
      while (++index < length) {
        var value = array[index],
            computed = iteratee ? iteratee(value, index, array) : value;

        if (isCommon && value === value) {
          var seenIndex = seen.length;
          while (seenIndex--) {
            if (seen[seenIndex] === computed) {
              continue outer;
            }
          }
          if (iteratee) {
            seen.push(computed);
          }
          result.push(value);
        }
        else if (indexOf(seen, computed, 0) < 0) {
          if (iteratee || isLarge) {
            seen.push(computed);
          }
          result.push(value);
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.values` and `_.valuesIn` which creates an
     * array of `object` property values corresponding to the property names
     * of `props`.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {Array} props The property names to get values for.
     * @returns {Object} Returns the array of property values.
     */
    function baseValues(object, props) {
      var index = -1,
          length = props.length,
          result = Array(length);

      while (++index < length) {
        result[index] = object[props[index]];
      }
      return result;
    }

    /**
     * The base implementation of `_.dropRightWhile`, `_.dropWhile`, `_.takeRightWhile`,
     * and `_.takeWhile` without support for callback shorthands and `this` binding.
     *
     * @private
     * @param {Array} array The array to query.
     * @param {Function} predicate The function invoked per iteration.
     * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Array} Returns the slice of `array`.
     */
    function baseWhile(array, predicate, isDrop, fromRight) {
      var length = array.length,
          index = fromRight ? length : -1;

      while ((fromRight ? index-- : ++index < length) && predicate(array[index], index, array)) {}
      return isDrop
        ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
        : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
    }

    /**
     * The base implementation of `wrapperValue` which returns the result of
     * performing a sequence of actions on the unwrapped `value`, where each
     * successive action is supplied the return value of the previous.
     *
     * @private
     * @param {*} value The unwrapped value.
     * @param {Array} actions Actions to peform to resolve the unwrapped value.
     * @returns {*} Returns the resolved value.
     */
    function baseWrapperValue(value, actions) {
      var result = value;
      if (result instanceof LazyWrapper) {
        result = result.value();
      }
      var index = -1,
          length = actions.length;

      while (++index < length) {
        var action = actions[index];
        result = action.func.apply(action.thisArg, arrayPush([result], action.args));
      }
      return result;
    }

    /**
     * Performs a binary search of `array` to determine the index at which `value`
     * should be inserted into `array` in order to maintain its sort order.
     *
     * @private
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @param {boolean} [retHighest] Specify returning the highest qualified index.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     */
    function binaryIndex(array, value, retHighest) {
      var low = 0,
          high = array ? array.length : low;

      if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
        while (low < high) {
          var mid = (low + high) >>> 1,
              computed = array[mid];

          if ((retHighest ? (computed <= value) : (computed < value)) && computed !== null) {
            low = mid + 1;
          } else {
            high = mid;
          }
        }
        return high;
      }
      return binaryIndexBy(array, value, identity, retHighest);
    }

    /**
     * This function is like `binaryIndex` except that it invokes `iteratee` for
     * `value` and each element of `array` to compute their sort ranking. The
     * iteratee is invoked with one argument; (value).
     *
     * @private
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {boolean} [retHighest] Specify returning the highest qualified index.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     */
    function binaryIndexBy(array, value, iteratee, retHighest) {
      value = iteratee(value);

      var low = 0,
          high = array ? array.length : 0,
          valIsNaN = value !== value,
          valIsNull = value === null,
          valIsUndef = value === undefined;

      while (low < high) {
        var mid = nativeFloor((low + high) / 2),
            computed = iteratee(array[mid]),
            isDef = computed !== undefined,
            isReflexive = computed === computed;

        if (valIsNaN) {
          var setLow = isReflexive || retHighest;
        } else if (valIsNull) {
          setLow = isReflexive && isDef && (retHighest || computed != null);
        } else if (valIsUndef) {
          setLow = isReflexive && (retHighest || isDef);
        } else if (computed == null) {
          setLow = false;
        } else {
          setLow = retHighest ? (computed <= value) : (computed < value);
        }
        if (setLow) {
          low = mid + 1;
        } else {
          high = mid;
        }
      }
      return nativeMin(high, MAX_ARRAY_INDEX);
    }

    /**
     * A specialized version of `baseCallback` which only supports `this` binding
     * and specifying the number of arguments to provide to `func`.
     *
     * @private
     * @param {Function} func The function to bind.
     * @param {*} thisArg The `this` binding of `func`.
     * @param {number} [argCount] The number of arguments to provide to `func`.
     * @returns {Function} Returns the callback.
     */
    function bindCallback(func, thisArg, argCount) {
      if (typeof func != 'function') {
        return identity;
      }
      if (thisArg === undefined) {
        return func;
      }
      switch (argCount) {
        case 1: return function(value) {
          return func.call(thisArg, value);
        };
        case 3: return function(value, index, collection) {
          return func.call(thisArg, value, index, collection);
        };
        case 4: return function(accumulator, value, index, collection) {
          return func.call(thisArg, accumulator, value, index, collection);
        };
        case 5: return function(value, other, key, object, source) {
          return func.call(thisArg, value, other, key, object, source);
        };
      }
      return function() {
        return func.apply(thisArg, arguments);
      };
    }

    /**
     * Creates a clone of the given array buffer.
     *
     * @private
     * @param {ArrayBuffer} buffer The array buffer to clone.
     * @returns {ArrayBuffer} Returns the cloned array buffer.
     */
    function bufferClone(buffer) {
      var result = new ArrayBuffer(buffer.byteLength),
          view = new Uint8Array(result);

      view.set(new Uint8Array(buffer));
      return result;
    }

    /**
     * Creates an array that is the composition of partially applied arguments,
     * placeholders, and provided arguments into a single array of arguments.
     *
     * @private
     * @param {Array|Object} args The provided arguments.
     * @param {Array} partials The arguments to prepend to those provided.
     * @param {Array} holders The `partials` placeholder indexes.
     * @returns {Array} Returns the new array of composed arguments.
     */
    function composeArgs(args, partials, holders) {
      var holdersLength = holders.length,
          argsIndex = -1,
          argsLength = nativeMax(args.length - holdersLength, 0),
          leftIndex = -1,
          leftLength = partials.length,
          result = Array(leftLength + argsLength);

      while (++leftIndex < leftLength) {
        result[leftIndex] = partials[leftIndex];
      }
      while (++argsIndex < holdersLength) {
        result[holders[argsIndex]] = args[argsIndex];
      }
      while (argsLength--) {
        result[leftIndex++] = args[argsIndex++];
      }
      return result;
    }

    /**
     * This function is like `composeArgs` except that the arguments composition
     * is tailored for `_.partialRight`.
     *
     * @private
     * @param {Array|Object} args The provided arguments.
     * @param {Array} partials The arguments to append to those provided.
     * @param {Array} holders The `partials` placeholder indexes.
     * @returns {Array} Returns the new array of composed arguments.
     */
    function composeArgsRight(args, partials, holders) {
      var holdersIndex = -1,
          holdersLength = holders.length,
          argsIndex = -1,
          argsLength = nativeMax(args.length - holdersLength, 0),
          rightIndex = -1,
          rightLength = partials.length,
          result = Array(argsLength + rightLength);

      while (++argsIndex < argsLength) {
        result[argsIndex] = args[argsIndex];
      }
      var offset = argsIndex;
      while (++rightIndex < rightLength) {
        result[offset + rightIndex] = partials[rightIndex];
      }
      while (++holdersIndex < holdersLength) {
        result[offset + holders[holdersIndex]] = args[argsIndex++];
      }
      return result;
    }

    /**
     * Creates a `_.countBy`, `_.groupBy`, `_.indexBy`, or `_.partition` function.
     *
     * @private
     * @param {Function} setter The function to set keys and values of the accumulator object.
     * @param {Function} [initializer] The function to initialize the accumulator object.
     * @returns {Function} Returns the new aggregator function.
     */
    function createAggregator(setter, initializer) {
      return function(collection, iteratee, thisArg) {
        var result = initializer ? initializer() : {};
        iteratee = getCallback(iteratee, thisArg, 3);

        if (isArray(collection)) {
          var index = -1,
              length = collection.length;

          while (++index < length) {
            var value = collection[index];
            setter(result, value, iteratee(value, index, collection), collection);
          }
        } else {
          baseEach(collection, function(value, key, collection) {
            setter(result, value, iteratee(value, key, collection), collection);
          });
        }
        return result;
      };
    }

    /**
     * Creates a `_.assign`, `_.defaults`, or `_.merge` function.
     *
     * @private
     * @param {Function} assigner The function to assign values.
     * @returns {Function} Returns the new assigner function.
     */
    function createAssigner(assigner) {
      return restParam(function(object, sources) {
        var index = -1,
            length = object == null ? 0 : sources.length,
            customizer = length > 2 ? sources[length - 2] : undefined,
            guard = length > 2 ? sources[2] : undefined,
            thisArg = length > 1 ? sources[length - 1] : undefined;

        if (typeof customizer == 'function') {
          customizer = bindCallback(customizer, thisArg, 5);
          length -= 2;
        } else {
          customizer = typeof thisArg == 'function' ? thisArg : undefined;
          length -= (customizer ? 1 : 0);
        }
        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
          customizer = length < 3 ? undefined : customizer;
          length = 1;
        }
        while (++index < length) {
          var source = sources[index];
          if (source) {
            assigner(object, source, customizer);
          }
        }
        return object;
      });
    }

    /**
     * Creates a `baseEach` or `baseEachRight` function.
     *
     * @private
     * @param {Function} eachFunc The function to iterate over a collection.
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Function} Returns the new base function.
     */
    function createBaseEach(eachFunc, fromRight) {
      return function(collection, iteratee) {
        var length = collection ? getLength(collection) : 0;
        if (!isLength(length)) {
          return eachFunc(collection, iteratee);
        }
        var index = fromRight ? length : -1,
            iterable = toObject(collection);

        while ((fromRight ? index-- : ++index < length)) {
          if (iteratee(iterable[index], index, iterable) === false) {
            break;
          }
        }
        return collection;
      };
    }

    /**
     * Creates a base function for `_.forIn` or `_.forInRight`.
     *
     * @private
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Function} Returns the new base function.
     */
    function createBaseFor(fromRight) {
      return function(object, iteratee, keysFunc) {
        var iterable = toObject(object),
            props = keysFunc(object),
            length = props.length,
            index = fromRight ? length : -1;

        while ((fromRight ? index-- : ++index < length)) {
          var key = props[index];
          if (iteratee(iterable[key], key, iterable) === false) {
            break;
          }
        }
        return object;
      };
    }

    /**
     * Creates a function that wraps `func` and invokes it with the `this`
     * binding of `thisArg`.
     *
     * @private
     * @param {Function} func The function to bind.
     * @param {*} [thisArg] The `this` binding of `func`.
     * @returns {Function} Returns the new bound function.
     */
    function createBindWrapper(func, thisArg) {
      var Ctor = createCtorWrapper(func);

      function wrapper() {
        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
        return fn.apply(thisArg, arguments);
      }
      return wrapper;
    }

    /**
     * Creates a `Set` cache object to optimize linear searches of large arrays.
     *
     * @private
     * @param {Array} [values] The values to cache.
     * @returns {null|Object} Returns the new cache object if `Set` is supported, else `null`.
     */
    function createCache(values) {
      return (nativeCreate && Set) ? new SetCache(values) : null;
    }

    /**
     * Creates a function that produces compound words out of the words in a
     * given string.
     *
     * @private
     * @param {Function} callback The function to combine each word.
     * @returns {Function} Returns the new compounder function.
     */
    function createCompounder(callback) {
      return function(string) {
        var index = -1,
            array = words(deburr(string)),
            length = array.length,
            result = '';

        while (++index < length) {
          result = callback(result, array[index], index);
        }
        return result;
      };
    }

    /**
     * Creates a function that produces an instance of `Ctor` regardless of
     * whether it was invoked as part of a `new` expression or by `call` or `apply`.
     *
     * @private
     * @param {Function} Ctor The constructor to wrap.
     * @returns {Function} Returns the new wrapped function.
     */
    function createCtorWrapper(Ctor) {
      return function() {
        // Use a `switch` statement to work with class constructors.
        // See http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
        // for more details.
        var args = arguments;
        switch (args.length) {
          case 0: return new Ctor;
          case 1: return new Ctor(args[0]);
          case 2: return new Ctor(args[0], args[1]);
          case 3: return new Ctor(args[0], args[1], args[2]);
          case 4: return new Ctor(args[0], args[1], args[2], args[3]);
          case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
          case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
          case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
        }
        var thisBinding = baseCreate(Ctor.prototype),
            result = Ctor.apply(thisBinding, args);

        // Mimic the constructor's `return` behavior.
        // See https://es5.github.io/#x13.2.2 for more details.
        return isObject(result) ? result : thisBinding;
      };
    }

    /**
     * Creates a `_.curry` or `_.curryRight` function.
     *
     * @private
     * @param {boolean} flag The curry bit flag.
     * @returns {Function} Returns the new curry function.
     */
    function createCurry(flag) {
      function curryFunc(func, arity, guard) {
        if (guard && isIterateeCall(func, arity, guard)) {
          arity = undefined;
        }
        var result = createWrapper(func, flag, undefined, undefined, undefined, undefined, undefined, arity);
        result.placeholder = curryFunc.placeholder;
        return result;
      }
      return curryFunc;
    }

    /**
     * Creates a `_.defaults` or `_.defaultsDeep` function.
     *
     * @private
     * @param {Function} assigner The function to assign values.
     * @param {Function} customizer The function to customize assigned values.
     * @returns {Function} Returns the new defaults function.
     */
    function createDefaults(assigner, customizer) {
      return restParam(function(args) {
        var object = args[0];
        if (object == null) {
          return object;
        }
        args.push(customizer);
        return assigner.apply(undefined, args);
      });
    }

    /**
     * Creates a `_.max` or `_.min` function.
     *
     * @private
     * @param {Function} comparator The function used to compare values.
     * @param {*} exValue The initial extremum value.
     * @returns {Function} Returns the new extremum function.
     */
    function createExtremum(comparator, exValue) {
      return function(collection, iteratee, thisArg) {
        if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
          iteratee = undefined;
        }
        iteratee = getCallback(iteratee, thisArg, 3);
        if (iteratee.length == 1) {
          collection = isArray(collection) ? collection : toIterable(collection);
          var result = arrayExtremum(collection, iteratee, comparator, exValue);
          if (!(collection.length && result === exValue)) {
            return result;
          }
        }
        return baseExtremum(collection, iteratee, comparator, exValue);
      };
    }

    /**
     * Creates a `_.find` or `_.findLast` function.
     *
     * @private
     * @param {Function} eachFunc The function to iterate over a collection.
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Function} Returns the new find function.
     */
    function createFind(eachFunc, fromRight) {
      return function(collection, predicate, thisArg) {
        predicate = getCallback(predicate, thisArg, 3);
        if (isArray(collection)) {
          var index = baseFindIndex(collection, predicate, fromRight);
          return index > -1 ? collection[index] : undefined;
        }
        return baseFind(collection, predicate, eachFunc);
      };
    }

    /**
     * Creates a `_.findIndex` or `_.findLastIndex` function.
     *
     * @private
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Function} Returns the new find function.
     */
    function createFindIndex(fromRight) {
      return function(array, predicate, thisArg) {
        if (!(array && array.length)) {
          return -1;
        }
        predicate = getCallback(predicate, thisArg, 3);
        return baseFindIndex(array, predicate, fromRight);
      };
    }

    /**
     * Creates a `_.findKey` or `_.findLastKey` function.
     *
     * @private
     * @param {Function} objectFunc The function to iterate over an object.
     * @returns {Function} Returns the new find function.
     */
    function createFindKey(objectFunc) {
      return function(object, predicate, thisArg) {
        predicate = getCallback(predicate, thisArg, 3);
        return baseFind(object, predicate, objectFunc, true);
      };
    }

    /**
     * Creates a `_.flow` or `_.flowRight` function.
     *
     * @private
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Function} Returns the new flow function.
     */
    function createFlow(fromRight) {
      return function() {
        var wrapper,
            length = arguments.length,
            index = fromRight ? length : -1,
            leftIndex = 0,
            funcs = Array(length);

        while ((fromRight ? index-- : ++index < length)) {
          var func = funcs[leftIndex++] = arguments[index];
          if (typeof func != 'function') {
            throw new TypeError(FUNC_ERROR_TEXT);
          }
          if (!wrapper && LodashWrapper.prototype.thru && getFuncName(func) == 'wrapper') {
            wrapper = new LodashWrapper([], true);
          }
        }
        index = wrapper ? -1 : length;
        while (++index < length) {
          func = funcs[index];

          var funcName = getFuncName(func),
              data = funcName == 'wrapper' ? getData(func) : undefined;

          if (data && isLaziable(data[0]) && data[1] == (ARY_FLAG | CURRY_FLAG | PARTIAL_FLAG | REARG_FLAG) && !data[4].length && data[9] == 1) {
            wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
          } else {
            wrapper = (func.length == 1 && isLaziable(func)) ? wrapper[funcName]() : wrapper.thru(func);
          }
        }
        return function() {
          var args = arguments,
              value = args[0];

          if (wrapper && args.length == 1 && isArray(value) && value.length >= LARGE_ARRAY_SIZE) {
            return wrapper.plant(value).value();
          }
          var index = 0,
              result = length ? funcs[index].apply(this, args) : value;

          while (++index < length) {
            result = funcs[index].call(this, result);
          }
          return result;
        };
      };
    }

    /**
     * Creates a function for `_.forEach` or `_.forEachRight`.
     *
     * @private
     * @param {Function} arrayFunc The function to iterate over an array.
     * @param {Function} eachFunc The function to iterate over a collection.
     * @returns {Function} Returns the new each function.
     */
    function createForEach(arrayFunc, eachFunc) {
      return function(collection, iteratee, thisArg) {
        return (typeof iteratee == 'function' && thisArg === undefined && isArray(collection))
          ? arrayFunc(collection, iteratee)
          : eachFunc(collection, bindCallback(iteratee, thisArg, 3));
      };
    }

    /**
     * Creates a function for `_.forIn` or `_.forInRight`.
     *
     * @private
     * @param {Function} objectFunc The function to iterate over an object.
     * @returns {Function} Returns the new each function.
     */
    function createForIn(objectFunc) {
      return function(object, iteratee, thisArg) {
        if (typeof iteratee != 'function' || thisArg !== undefined) {
          iteratee = bindCallback(iteratee, thisArg, 3);
        }
        return objectFunc(object, iteratee, keysIn);
      };
    }

    /**
     * Creates a function for `_.forOwn` or `_.forOwnRight`.
     *
     * @private
     * @param {Function} objectFunc The function to iterate over an object.
     * @returns {Function} Returns the new each function.
     */
    function createForOwn(objectFunc) {
      return function(object, iteratee, thisArg) {
        if (typeof iteratee != 'function' || thisArg !== undefined) {
          iteratee = bindCallback(iteratee, thisArg, 3);
        }
        return objectFunc(object, iteratee);
      };
    }

    /**
     * Creates a function for `_.mapKeys` or `_.mapValues`.
     *
     * @private
     * @param {boolean} [isMapKeys] Specify mapping keys instead of values.
     * @returns {Function} Returns the new map function.
     */
    function createObjectMapper(isMapKeys) {
      return function(object, iteratee, thisArg) {
        var result = {};
        iteratee = getCallback(iteratee, thisArg, 3);

        baseForOwn(object, function(value, key, object) {
          var mapped = iteratee(value, key, object);
          key = isMapKeys ? mapped : key;
          value = isMapKeys ? value : mapped;
          result[key] = value;
        });
        return result;
      };
    }

    /**
     * Creates a function for `_.padLeft` or `_.padRight`.
     *
     * @private
     * @param {boolean} [fromRight] Specify padding from the right.
     * @returns {Function} Returns the new pad function.
     */
    function createPadDir(fromRight) {
      return function(string, length, chars) {
        string = baseToString(string);
        return (fromRight ? string : '') + createPadding(string, length, chars) + (fromRight ? '' : string);
      };
    }

    /**
     * Creates a `_.partial` or `_.partialRight` function.
     *
     * @private
     * @param {boolean} flag The partial bit flag.
     * @returns {Function} Returns the new partial function.
     */
    function createPartial(flag) {
      var partialFunc = restParam(function(func, partials) {
        var holders = replaceHolders(partials, partialFunc.placeholder);
        return createWrapper(func, flag, undefined, partials, holders);
      });
      return partialFunc;
    }

    /**
     * Creates a function for `_.reduce` or `_.reduceRight`.
     *
     * @private
     * @param {Function} arrayFunc The function to iterate over an array.
     * @param {Function} eachFunc The function to iterate over a collection.
     * @returns {Function} Returns the new each function.
     */
    function createReduce(arrayFunc, eachFunc) {
      return function(collection, iteratee, accumulator, thisArg) {
        var initFromArray = arguments.length < 3;
        return (typeof iteratee == 'function' && thisArg === undefined && isArray(collection))
          ? arrayFunc(collection, iteratee, accumulator, initFromArray)
          : baseReduce(collection, getCallback(iteratee, thisArg, 4), accumulator, initFromArray, eachFunc);
      };
    }

    /**
     * Creates a function that wraps `func` and invokes it with optional `this`
     * binding of, partial application, and currying.
     *
     * @private
     * @param {Function|string} func The function or method name to reference.
     * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
     * @param {*} [thisArg] The `this` binding of `func`.
     * @param {Array} [partials] The arguments to prepend to those provided to the new function.
     * @param {Array} [holders] The `partials` placeholder indexes.
     * @param {Array} [partialsRight] The arguments to append to those provided to the new function.
     * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
     * @param {Array} [argPos] The argument positions of the new function.
     * @param {number} [ary] The arity cap of `func`.
     * @param {number} [arity] The arity of `func`.
     * @returns {Function} Returns the new wrapped function.
     */
    function createHybridWrapper(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
      var isAry = bitmask & ARY_FLAG,
          isBind = bitmask & BIND_FLAG,
          isBindKey = bitmask & BIND_KEY_FLAG,
          isCurry = bitmask & CURRY_FLAG,
          isCurryBound = bitmask & CURRY_BOUND_FLAG,
          isCurryRight = bitmask & CURRY_RIGHT_FLAG,
          Ctor = isBindKey ? undefined : createCtorWrapper(func);

      function wrapper() {
        // Avoid `arguments` object use disqualifying optimizations by
        // converting it to an array before providing it to other functions.
        var length = arguments.length,
            index = length,
            args = Array(length);

        while (index--) {
          args[index] = arguments[index];
        }
        if (partials) {
          args = composeArgs(args, partials, holders);
        }
        if (partialsRight) {
          args = composeArgsRight(args, partialsRight, holdersRight);
        }
        if (isCurry || isCurryRight) {
          var placeholder = wrapper.placeholder,
              argsHolders = replaceHolders(args, placeholder);

          length -= argsHolders.length;
          if (length < arity) {
            var newArgPos = argPos ? arrayCopy(argPos) : undefined,
                newArity = nativeMax(arity - length, 0),
                newsHolders = isCurry ? argsHolders : undefined,
                newHoldersRight = isCurry ? undefined : argsHolders,
                newPartials = isCurry ? args : undefined,
                newPartialsRight = isCurry ? undefined : args;

            bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG);
            bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG);

            if (!isCurryBound) {
              bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
            }
            var newData = [func, bitmask, thisArg, newPartials, newsHolders, newPartialsRight, newHoldersRight, newArgPos, ary, newArity],
                result = createHybridWrapper.apply(undefined, newData);

            if (isLaziable(func)) {
              setData(result, newData);
            }
            result.placeholder = placeholder;
            return result;
          }
        }
        var thisBinding = isBind ? thisArg : this,
            fn = isBindKey ? thisBinding[func] : func;

        if (argPos) {
          args = reorder(args, argPos);
        }
        if (isAry && ary < args.length) {
          args.length = ary;
        }
        if (this && this !== root && this instanceof wrapper) {
          fn = Ctor || createCtorWrapper(func);
        }
        return fn.apply(thisBinding, args);
      }
      return wrapper;
    }

    /**
     * Creates the padding required for `string` based on the given `length`.
     * The `chars` string is truncated if the number of characters exceeds `length`.
     *
     * @private
     * @param {string} string The string to create padding for.
     * @param {number} [length=0] The padding length.
     * @param {string} [chars=' '] The string used as padding.
     * @returns {string} Returns the pad for `string`.
     */
    function createPadding(string, length, chars) {
      var strLength = string.length;
      length = +length;

      if (strLength >= length || !nativeIsFinite(length)) {
        return '';
      }
      var padLength = length - strLength;
      chars = chars == null ? ' ' : (chars + '');
      return repeat(chars, nativeCeil(padLength / chars.length)).slice(0, padLength);
    }

    /**
     * Creates a function that wraps `func` and invokes it with the optional `this`
     * binding of `thisArg` and the `partials` prepended to those provided to
     * the wrapper.
     *
     * @private
     * @param {Function} func The function to partially apply arguments to.
     * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
     * @param {*} thisArg The `this` binding of `func`.
     * @param {Array} partials The arguments to prepend to those provided to the new function.
     * @returns {Function} Returns the new bound function.
     */
    function createPartialWrapper(func, bitmask, thisArg, partials) {
      var isBind = bitmask & BIND_FLAG,
          Ctor = createCtorWrapper(func);

      function wrapper() {
        // Avoid `arguments` object use disqualifying optimizations by
        // converting it to an array before providing it `func`.
        var argsIndex = -1,
            argsLength = arguments.length,
            leftIndex = -1,
            leftLength = partials.length,
            args = Array(leftLength + argsLength);

        while (++leftIndex < leftLength) {
          args[leftIndex] = partials[leftIndex];
        }
        while (argsLength--) {
          args[leftIndex++] = arguments[++argsIndex];
        }
        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
        return fn.apply(isBind ? thisArg : this, args);
      }
      return wrapper;
    }

    /**
     * Creates a `_.ceil`, `_.floor`, or `_.round` function.
     *
     * @private
     * @param {string} methodName The name of the `Math` method to use when rounding.
     * @returns {Function} Returns the new round function.
     */
    function createRound(methodName) {
      var func = Math[methodName];
      return function(number, precision) {
        precision = precision === undefined ? 0 : (+precision || 0);
        if (precision) {
          precision = pow(10, precision);
          return func(number * precision) / precision;
        }
        return func(number);
      };
    }

    /**
     * Creates a `_.sortedIndex` or `_.sortedLastIndex` function.
     *
     * @private
     * @param {boolean} [retHighest] Specify returning the highest qualified index.
     * @returns {Function} Returns the new index function.
     */
    function createSortedIndex(retHighest) {
      return function(array, value, iteratee, thisArg) {
        var callback = getCallback(iteratee);
        return (iteratee == null && callback === baseCallback)
          ? binaryIndex(array, value, retHighest)
          : binaryIndexBy(array, value, callback(iteratee, thisArg, 1), retHighest);
      };
    }

    /**
     * Creates a function that either curries or invokes `func` with optional
     * `this` binding and partially applied arguments.
     *
     * @private
     * @param {Function|string} func The function or method name to reference.
     * @param {number} bitmask The bitmask of flags.
     *  The bitmask may be composed of the following flags:
     *     1 - `_.bind`
     *     2 - `_.bindKey`
     *     4 - `_.curry` or `_.curryRight` of a bound function
     *     8 - `_.curry`
     *    16 - `_.curryRight`
     *    32 - `_.partial`
     *    64 - `_.partialRight`
     *   128 - `_.rearg`
     *   256 - `_.ary`
     * @param {*} [thisArg] The `this` binding of `func`.
     * @param {Array} [partials] The arguments to be partially applied.
     * @param {Array} [holders] The `partials` placeholder indexes.
     * @param {Array} [argPos] The argument positions of the new function.
     * @param {number} [ary] The arity cap of `func`.
     * @param {number} [arity] The arity of `func`.
     * @returns {Function} Returns the new wrapped function.
     */
    function createWrapper(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
      var isBindKey = bitmask & BIND_KEY_FLAG;
      if (!isBindKey && typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      var length = partials ? partials.length : 0;
      if (!length) {
        bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG);
        partials = holders = undefined;
      }
      length -= (holders ? holders.length : 0);
      if (bitmask & PARTIAL_RIGHT_FLAG) {
        var partialsRight = partials,
            holdersRight = holders;

        partials = holders = undefined;
      }
      var data = isBindKey ? undefined : getData(func),
          newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];

      if (data) {
        mergeData(newData, data);
        bitmask = newData[1];
        arity = newData[9];
      }
      newData[9] = arity == null
        ? (isBindKey ? 0 : func.length)
        : (nativeMax(arity - length, 0) || 0);

      if (bitmask == BIND_FLAG) {
        var result = createBindWrapper(newData[0], newData[2]);
      } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !newData[4].length) {
        result = createPartialWrapper.apply(undefined, newData);
      } else {
        result = createHybridWrapper.apply(undefined, newData);
      }
      var setter = data ? baseSetData : setData;
      return setter(result, newData);
    }

    /**
     * A specialized version of `baseIsEqualDeep` for arrays with support for
     * partial deep comparisons.
     *
     * @private
     * @param {Array} array The array to compare.
     * @param {Array} other The other array to compare.
     * @param {Function} equalFunc The function to determine equivalents of values.
     * @param {Function} [customizer] The function to customize comparing arrays.
     * @param {boolean} [isLoose] Specify performing partial comparisons.
     * @param {Array} [stackA] Tracks traversed `value` objects.
     * @param {Array} [stackB] Tracks traversed `other` objects.
     * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
     */
    function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) {
      var index = -1,
          arrLength = array.length,
          othLength = other.length;

      if (arrLength != othLength && !(isLoose && othLength > arrLength)) {
        return false;
      }
      // Ignore non-index properties.
      while (++index < arrLength) {
        var arrValue = array[index],
            othValue = other[index],
            result = customizer ? customizer(isLoose ? othValue : arrValue, isLoose ? arrValue : othValue, index) : undefined;

        if (result !== undefined) {
          if (result) {
            continue;
          }
          return false;
        }
        // Recursively compare arrays (susceptible to call stack limits).
        if (isLoose) {
          if (!arraySome(other, function(othValue) {
                return arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
              })) {
            return false;
          }
        } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB))) {
          return false;
        }
      }
      return true;
    }

    /**
     * A specialized version of `baseIsEqualDeep` for comparing objects of
     * the same `toStringTag`.
     *
     * **Note:** This function only supports comparing values with tags of
     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
     *
     * @private
     * @param {Object} object The object to compare.
     * @param {Object} other The other object to compare.
     * @param {string} tag The `toStringTag` of the objects to compare.
     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
     */
    function equalByTag(object, other, tag) {
      switch (tag) {
        case boolTag:
        case dateTag:
          // Coerce dates and booleans to numbers, dates to milliseconds and booleans
          // to `1` or `0` treating invalid dates coerced to `NaN` as not equal.
          return +object == +other;

        case errorTag:
          return object.name == other.name && object.message == other.message;

        case numberTag:
          // Treat `NaN` vs. `NaN` as equal.
          return (object != +object)
            ? other != +other
            : object == +other;

        case regexpTag:
        case stringTag:
          // Coerce regexes to strings and treat strings primitives and string
          // objects as equal. See https://es5.github.io/#x15.10.6.4 for more details.
          return object == (other + '');
      }
      return false;
    }

    /**
     * A specialized version of `baseIsEqualDeep` for objects with support for
     * partial deep comparisons.
     *
     * @private
     * @param {Object} object The object to compare.
     * @param {Object} other The other object to compare.
     * @param {Function} equalFunc The function to determine equivalents of values.
     * @param {Function} [customizer] The function to customize comparing values.
     * @param {boolean} [isLoose] Specify performing partial comparisons.
     * @param {Array} [stackA] Tracks traversed `value` objects.
     * @param {Array} [stackB] Tracks traversed `other` objects.
     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
     */
    function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
      var objProps = keys(object),
          objLength = objProps.length,
          othProps = keys(other),
          othLength = othProps.length;

      if (objLength != othLength && !isLoose) {
        return false;
      }
      var index = objLength;
      while (index--) {
        var key = objProps[index];
        if (!(isLoose ? key in other : hasOwnProperty.call(other, key))) {
          return false;
        }
      }
      var skipCtor = isLoose;
      while (++index < objLength) {
        key = objProps[index];
        var objValue = object[key],
            othValue = other[key],
            result = customizer ? customizer(isLoose ? othValue : objValue, isLoose? objValue : othValue, key) : undefined;

        // Recursively compare objects (susceptible to call stack limits).
        if (!(result === undefined ? equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB) : result)) {
          return false;
        }
        skipCtor || (skipCtor = key == 'constructor');
      }
      if (!skipCtor) {
        var objCtor = object.constructor,
            othCtor = other.constructor;

        // Non `Object` object instances with different constructors are not equal.
        if (objCtor != othCtor &&
            ('constructor' in object && 'constructor' in other) &&
            !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
              typeof othCtor == 'function' && othCtor instanceof othCtor)) {
          return false;
        }
      }
      return true;
    }

    /**
     * Gets the appropriate "callback" function. If the `_.callback` method is
     * customized this function returns the custom method, otherwise it returns
     * the `baseCallback` function. If arguments are provided the chosen function
     * is invoked with them and its result is returned.
     *
     * @private
     * @returns {Function} Returns the chosen function or its result.
     */
    function getCallback(func, thisArg, argCount) {
      var result = lodash.callback || callback;
      result = result === callback ? baseCallback : result;
      return argCount ? result(func, thisArg, argCount) : result;
    }

    /**
     * Gets metadata for `func`.
     *
     * @private
     * @param {Function} func The function to query.
     * @returns {*} Returns the metadata for `func`.
     */
    var getData = !metaMap ? noop : function(func) {
      return metaMap.get(func);
    };

    /**
     * Gets the name of `func`.
     *
     * @private
     * @param {Function} func The function to query.
     * @returns {string} Returns the function name.
     */
    function getFuncName(func) {
      var result = func.name,
          array = realNames[result],
          length = array ? array.length : 0;

      while (length--) {
        var data = array[length],
            otherFunc = data.func;
        if (otherFunc == null || otherFunc == func) {
          return data.name;
        }
      }
      return result;
    }

    /**
     * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
     * customized this function returns the custom method, otherwise it returns
     * the `baseIndexOf` function. If arguments are provided the chosen function
     * is invoked with them and its result is returned.
     *
     * @private
     * @returns {Function|number} Returns the chosen function or its result.
     */
    function getIndexOf(collection, target, fromIndex) {
      var result = lodash.indexOf || indexOf;
      result = result === indexOf ? baseIndexOf : result;
      return collection ? result(collection, target, fromIndex) : result;
    }

    /**
     * Gets the "length" property value of `object`.
     *
     * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
     * that affects Safari on at least iOS 8.1-8.3 ARM64.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {*} Returns the "length" value.
     */
    var getLength = baseProperty('length');

    /**
     * Gets the propery names, values, and compare flags of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the match data of `object`.
     */
    function getMatchData(object) {
      var result = pairs(object),
          length = result.length;

      while (length--) {
        result[length][2] = isStrictComparable(result[length][1]);
      }
      return result;
    }

    /**
     * Gets the native function at `key` of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {string} key The key of the method to get.
     * @returns {*} Returns the function if it's native, else `undefined`.
     */
    function getNative(object, key) {
      var value = object == null ? undefined : object[key];
      return isNative(value) ? value : undefined;
    }

    /**
     * Gets the view, applying any `transforms` to the `start` and `end` positions.
     *
     * @private
     * @param {number} start The start of the view.
     * @param {number} end The end of the view.
     * @param {Array} transforms The transformations to apply to the view.
     * @returns {Object} Returns an object containing the `start` and `end`
     *  positions of the view.
     */
    function getView(start, end, transforms) {
      var index = -1,
          length = transforms.length;

      while (++index < length) {
        var data = transforms[index],
            size = data.size;

        switch (data.type) {
          case 'drop':      start += size; break;
          case 'dropRight': end -= size; break;
          case 'take':      end = nativeMin(end, start + size); break;
          case 'takeRight': start = nativeMax(start, end - size); break;
        }
      }
      return { 'start': start, 'end': end };
    }

    /**
     * Initializes an array clone.
     *
     * @private
     * @param {Array} array The array to clone.
     * @returns {Array} Returns the initialized clone.
     */
    function initCloneArray(array) {
      var length = array.length,
          result = new array.constructor(length);

      // Add array properties assigned by `RegExp#exec`.
      if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
        result.index = array.index;
        result.input = array.input;
      }
      return result;
    }

    /**
     * Initializes an object clone.
     *
     * @private
     * @param {Object} object The object to clone.
     * @returns {Object} Returns the initialized clone.
     */
    function initCloneObject(object) {
      var Ctor = object.constructor;
      if (!(typeof Ctor == 'function' && Ctor instanceof Ctor)) {
        Ctor = Object;
      }
      return new Ctor;
    }

    /**
     * Initializes an object clone based on its `toStringTag`.
     *
     * **Note:** This function only supports cloning values with tags of
     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
     *
     * @private
     * @param {Object} object The object to clone.
     * @param {string} tag The `toStringTag` of the object to clone.
     * @param {boolean} [isDeep] Specify a deep clone.
     * @returns {Object} Returns the initialized clone.
     */
    function initCloneByTag(object, tag, isDeep) {
      var Ctor = object.constructor;
      switch (tag) {
        case arrayBufferTag:
          return bufferClone(object);

        case boolTag:
        case dateTag:
          return new Ctor(+object);

        case float32Tag: case float64Tag:
        case int8Tag: case int16Tag: case int32Tag:
        case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
          var buffer = object.buffer;
          return new Ctor(isDeep ? bufferClone(buffer) : buffer, object.byteOffset, object.length);

        case numberTag:
        case stringTag:
          return new Ctor(object);

        case regexpTag:
          var result = new Ctor(object.source, reFlags.exec(object));
          result.lastIndex = object.lastIndex;
      }
      return result;
    }

    /**
     * Invokes the method at `path` on `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {Array|string} path The path of the method to invoke.
     * @param {Array} args The arguments to invoke the method with.
     * @returns {*} Returns the result of the invoked method.
     */
    function invokePath(object, path, args) {
      if (object != null && !isKey(path, object)) {
        path = toPath(path);
        object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
        path = last(path);
      }
      var func = object == null ? object : object[path];
      return func == null ? undefined : func.apply(object, args);
    }

    /**
     * Checks if `value` is array-like.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
     */
    function isArrayLike(value) {
      return value != null && isLength(getLength(value));
    }

    /**
     * Checks if `value` is a valid array-like index.
     *
     * @private
     * @param {*} value The value to check.
     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
     */
    function isIndex(value, length) {
      value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
      length = length == null ? MAX_SAFE_INTEGER : length;
      return value > -1 && value % 1 == 0 && value < length;
    }

    /**
     * Checks if the provided arguments are from an iteratee call.
     *
     * @private
     * @param {*} value The potential iteratee value argument.
     * @param {*} index The potential iteratee index or key argument.
     * @param {*} object The potential iteratee object argument.
     * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.
     */
    function isIterateeCall(value, index, object) {
      if (!isObject(object)) {
        return false;
      }
      var type = typeof index;
      if (type == 'number'
          ? (isArrayLike(object) && isIndex(index, object.length))
          : (type == 'string' && index in object)) {
        var other = object[index];
        return value === value ? (value === other) : (other !== other);
      }
      return false;
    }

    /**
     * Checks if `value` is a property name and not a property path.
     *
     * @private
     * @param {*} value The value to check.
     * @param {Object} [object] The object to query keys on.
     * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
     */
    function isKey(value, object) {
      var type = typeof value;
      if ((type == 'string' && reIsPlainProp.test(value)) || type == 'number') {
        return true;
      }
      if (isArray(value)) {
        return false;
      }
      var result = !reIsDeepProp.test(value);
      return result || (object != null && value in toObject(object));
    }

    /**
     * Checks if `func` has a lazy counterpart.
     *
     * @private
     * @param {Function} func The function to check.
     * @returns {boolean} Returns `true` if `func` has a lazy counterpart, else `false`.
     */
    function isLaziable(func) {
      var funcName = getFuncName(func);
      if (!(funcName in LazyWrapper.prototype)) {
        return false;
      }
      var other = lodash[funcName];
      if (func === other) {
        return true;
      }
      var data = getData(other);
      return !!data && func === data[0];
    }

    /**
     * Checks if `value` is a valid array-like length.
     *
     * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
     */
    function isLength(value) {
      return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
    }

    /**
     * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` if suitable for strict
     *  equality comparisons, else `false`.
     */
    function isStrictComparable(value) {
      return value === value && !isObject(value);
    }

    /**
     * Merges the function metadata of `source` into `data`.
     *
     * Merging metadata reduces the number of wrappers required to invoke a function.
     * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
     * may be applied regardless of execution order. Methods like `_.ary` and `_.rearg`
     * augment function arguments, making the order in which they are executed important,
     * preventing the merging of metadata. However, we make an exception for a safe
     * common case where curried functions have `_.ary` and or `_.rearg` applied.
     *
     * @private
     * @param {Array} data The destination metadata.
     * @param {Array} source The source metadata.
     * @returns {Array} Returns `data`.
     */
    function mergeData(data, source) {
      var bitmask = data[1],
          srcBitmask = source[1],
          newBitmask = bitmask | srcBitmask,
          isCommon = newBitmask < ARY_FLAG;

      var isCombo =
        (srcBitmask == ARY_FLAG && bitmask == CURRY_FLAG) ||
        (srcBitmask == ARY_FLAG && bitmask == REARG_FLAG && data[7].length <= source[8]) ||
        (srcBitmask == (ARY_FLAG | REARG_FLAG) && bitmask == CURRY_FLAG);

      // Exit early if metadata can't be merged.
      if (!(isCommon || isCombo)) {
        return data;
      }
      // Use source `thisArg` if available.
      if (srcBitmask & BIND_FLAG) {
        data[2] = source[2];
        // Set when currying a bound function.
        newBitmask |= (bitmask & BIND_FLAG) ? 0 : CURRY_BOUND_FLAG;
      }
      // Compose partial arguments.
      var value = source[3];
      if (value) {
        var partials = data[3];
        data[3] = partials ? composeArgs(partials, value, source[4]) : arrayCopy(value);
        data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : arrayCopy(source[4]);
      }
      // Compose partial right arguments.
      value = source[5];
      if (value) {
        partials = data[5];
        data[5] = partials ? composeArgsRight(partials, value, source[6]) : arrayCopy(value);
        data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : arrayCopy(source[6]);
      }
      // Use source `argPos` if available.
      value = source[7];
      if (value) {
        data[7] = arrayCopy(value);
      }
      // Use source `ary` if it's smaller.
      if (srcBitmask & ARY_FLAG) {
        data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
      }
      // Use source `arity` if one is not provided.
      if (data[9] == null) {
        data[9] = source[9];
      }
      // Use source `func` and merge bitmasks.
      data[0] = source[0];
      data[1] = newBitmask;

      return data;
    }

    /**
     * Used by `_.defaultsDeep` to customize its `_.merge` use.
     *
     * @private
     * @param {*} objectValue The destination object property value.
     * @param {*} sourceValue The source object property value.
     * @returns {*} Returns the value to assign to the destination object.
     */
    function mergeDefaults(objectValue, sourceValue) {
      return objectValue === undefined ? sourceValue : merge(objectValue, sourceValue, mergeDefaults);
    }

    /**
     * A specialized version of `_.pick` which picks `object` properties specified
     * by `props`.
     *
     * @private
     * @param {Object} object The source object.
     * @param {string[]} props The property names to pick.
     * @returns {Object} Returns the new object.
     */
    function pickByArray(object, props) {
      object = toObject(object);

      var index = -1,
          length = props.length,
          result = {};

      while (++index < length) {
        var key = props[index];
        if (key in object) {
          result[key] = object[key];
        }
      }
      return result;
    }

    /**
     * A specialized version of `_.pick` which picks `object` properties `predicate`
     * returns truthy for.
     *
     * @private
     * @param {Object} object The source object.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {Object} Returns the new object.
     */
    function pickByCallback(object, predicate) {
      var result = {};
      baseForIn(object, function(value, key, object) {
        if (predicate(value, key, object)) {
          result[key] = value;
        }
      });
      return result;
    }

    /**
     * Reorder `array` according to the specified indexes where the element at
     * the first index is assigned as the first element, the element at
     * the second index is assigned as the second element, and so on.
     *
     * @private
     * @param {Array} array The array to reorder.
     * @param {Array} indexes The arranged array indexes.
     * @returns {Array} Returns `array`.
     */
    function reorder(array, indexes) {
      var arrLength = array.length,
          length = nativeMin(indexes.length, arrLength),
          oldArray = arrayCopy(array);

      while (length--) {
        var index = indexes[length];
        array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
      }
      return array;
    }

    /**
     * Sets metadata for `func`.
     *
     * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
     * period of time, it will trip its breaker and transition to an identity function
     * to avoid garbage collection pauses in V8. See [V8 issue 2070](https://code.google.com/p/v8/issues/detail?id=2070)
     * for more details.
     *
     * @private
     * @param {Function} func The function to associate metadata with.
     * @param {*} data The metadata.
     * @returns {Function} Returns `func`.
     */
    var setData = (function() {
      var count = 0,
          lastCalled = 0;

      return function(key, value) {
        var stamp = now(),
            remaining = HOT_SPAN - (stamp - lastCalled);

        lastCalled = stamp;
        if (remaining > 0) {
          if (++count >= HOT_COUNT) {
            return key;
          }
        } else {
          count = 0;
        }
        return baseSetData(key, value);
      };
    }());

    /**
     * A fallback implementation of `Object.keys` which creates an array of the
     * own enumerable property names of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names.
     */
    function shimKeys(object) {
      var props = keysIn(object),
          propsLength = props.length,
          length = propsLength && object.length;

      var allowIndexes = !!length && isLength(length) &&
        (isArray(object) || isArguments(object));

      var index = -1,
          result = [];

      while (++index < propsLength) {
        var key = props[index];
        if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) {
          result.push(key);
        }
      }
      return result;
    }

    /**
     * Converts `value` to an array-like object if it's not one.
     *
     * @private
     * @param {*} value The value to process.
     * @returns {Array|Object} Returns the array-like object.
     */
    function toIterable(value) {
      if (value == null) {
        return [];
      }
      if (!isArrayLike(value)) {
        return values(value);
      }
      return isObject(value) ? value : Object(value);
    }

    /**
     * Converts `value` to an object if it's not one.
     *
     * @private
     * @param {*} value The value to process.
     * @returns {Object} Returns the object.
     */
    function toObject(value) {
      return isObject(value) ? value : Object(value);
    }

    /**
     * Converts `value` to property path array if it's not one.
     *
     * @private
     * @param {*} value The value to process.
     * @returns {Array} Returns the property path array.
     */
    function toPath(value) {
      if (isArray(value)) {
        return value;
      }
      var result = [];
      baseToString(value).replace(rePropName, function(match, number, quote, string) {
        result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
      });
      return result;
    }

    /**
     * Creates a clone of `wrapper`.
     *
     * @private
     * @param {Object} wrapper The wrapper to clone.
     * @returns {Object} Returns the cloned wrapper.
     */
    function wrapperClone(wrapper) {
      return wrapper instanceof LazyWrapper
        ? wrapper.clone()
        : new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__, arrayCopy(wrapper.__actions__));
    }

    /*------------------------------------------------------------------------*/

    /**
     * Creates an array of elements split into groups the length of `size`.
     * If `collection` can't be split evenly, the final chunk will be the remaining
     * elements.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to process.
     * @param {number} [size=1] The length of each chunk.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Array} Returns the new array containing chunks.
     * @example
     *
     * _.chunk(['a', 'b', 'c', 'd'], 2);
     * // => [['a', 'b'], ['c', 'd']]
     *
     * _.chunk(['a', 'b', 'c', 'd'], 3);
     * // => [['a', 'b', 'c'], ['d']]
     */
    function chunk(array, size, guard) {
      if (guard ? isIterateeCall(array, size, guard) : size == null) {
        size = 1;
      } else {
        size = nativeMax(nativeFloor(size) || 1, 1);
      }
      var index = 0,
          length = array ? array.length : 0,
          resIndex = -1,
          result = Array(nativeCeil(length / size));

      while (index < length) {
        result[++resIndex] = baseSlice(array, index, (index += size));
      }
      return result;
    }

    /**
     * Creates an array with all falsey values removed. The values `false`, `null`,
     * `0`, `""`, `undefined`, and `NaN` are falsey.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to compact.
     * @returns {Array} Returns the new array of filtered values.
     * @example
     *
     * _.compact([0, 1, false, 2, '', 3]);
     * // => [1, 2, 3]
     */
    function compact(array) {
      var index = -1,
          length = array ? array.length : 0,
          resIndex = -1,
          result = [];

      while (++index < length) {
        var value = array[index];
        if (value) {
          result[++resIndex] = value;
        }
      }
      return result;
    }

    /**
     * Creates an array of unique `array` values not included in the other
     * provided arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
     * for equality comparisons.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {...Array} [values] The arrays of values to exclude.
     * @returns {Array} Returns the new array of filtered values.
     * @example
     *
     * _.difference([1, 2, 3], [4, 2]);
     * // => [1, 3]
     */
    var difference = restParam(function(array, values) {
      return (isObjectLike(array) && isArrayLike(array))
        ? baseDifference(array, baseFlatten(values, false, true))
        : [];
    });

    /**
     * Creates a slice of `array` with `n` elements dropped from the beginning.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @param {number} [n=1] The number of elements to drop.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.drop([1, 2, 3]);
     * // => [2, 3]
     *
     * _.drop([1, 2, 3], 2);
     * // => [3]
     *
     * _.drop([1, 2, 3], 5);
     * // => []
     *
     * _.drop([1, 2, 3], 0);
     * // => [1, 2, 3]
     */
    function drop(array, n, guard) {
      var length = array ? array.length : 0;
      if (!length) {
        return [];
      }
      if (guard ? isIterateeCall(array, n, guard) : n == null) {
        n = 1;
      }
      return baseSlice(array, n < 0 ? 0 : n);
    }

    /**
     * Creates a slice of `array` with `n` elements dropped from the end.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @param {number} [n=1] The number of elements to drop.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.dropRight([1, 2, 3]);
     * // => [1, 2]
     *
     * _.dropRight([1, 2, 3], 2);
     * // => [1]
     *
     * _.dropRight([1, 2, 3], 5);
     * // => []
     *
     * _.dropRight([1, 2, 3], 0);
     * // => [1, 2, 3]
     */
    function dropRight(array, n, guard) {
      var length = array ? array.length : 0;
      if (!length) {
        return [];
      }
      if (guard ? isIterateeCall(array, n, guard) : n == null) {
        n = 1;
      }
      n = length - (+n || 0);
      return baseSlice(array, 0, n < 0 ? 0 : n);
    }

    /**
     * Creates a slice of `array` excluding elements dropped from the end.
     * Elements are dropped until `predicate` returns falsey. The predicate is
     * bound to `thisArg` and invoked with three arguments: (value, index, array).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that match the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.dropRightWhile([1, 2, 3], function(n) {
     *   return n > 1;
     * });
     * // => [1]
     *
     * var users = [
     *   { 'user': 'barney',  'active': true },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': false }
     * ];
     *
     * // using the `_.matches` callback shorthand
     * _.pluck(_.dropRightWhile(users, { 'user': 'pebbles', 'active': false }), 'user');
     * // => ['barney', 'fred']
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.pluck(_.dropRightWhile(users, 'active', false), 'user');
     * // => ['barney']
     *
     * // using the `_.property` callback shorthand
     * _.pluck(_.dropRightWhile(users, 'active'), 'user');
     * // => ['barney', 'fred', 'pebbles']
     */
    function dropRightWhile(array, predicate, thisArg) {
      return (array && array.length)
        ? baseWhile(array, getCallback(predicate, thisArg, 3), true, true)
        : [];
    }

    /**
     * Creates a slice of `array` excluding elements dropped from the beginning.
     * Elements are dropped until `predicate` returns falsey. The predicate is
     * bound to `thisArg` and invoked with three arguments: (value, index, array).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.dropWhile([1, 2, 3], function(n) {
     *   return n < 3;
     * });
     * // => [3]
     *
     * var users = [
     *   { 'user': 'barney',  'active': false },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': true }
     * ];
     *
     * // using the `_.matches` callback shorthand
     * _.pluck(_.dropWhile(users, { 'user': 'barney', 'active': false }), 'user');
     * // => ['fred', 'pebbles']
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.pluck(_.dropWhile(users, 'active', false), 'user');
     * // => ['pebbles']
     *
     * // using the `_.property` callback shorthand
     * _.pluck(_.dropWhile(users, 'active'), 'user');
     * // => ['barney', 'fred', 'pebbles']
     */
    function dropWhile(array, predicate, thisArg) {
      return (array && array.length)
        ? baseWhile(array, getCallback(predicate, thisArg, 3), true)
        : [];
    }

    /**
     * Fills elements of `array` with `value` from `start` up to, but not
     * including, `end`.
     *
     * **Note:** This method mutates `array`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to fill.
     * @param {*} value The value to fill `array` with.
     * @param {number} [start=0] The start position.
     * @param {number} [end=array.length] The end position.
     * @returns {Array} Returns `array`.
     * @example
     *
     * var array = [1, 2, 3];
     *
     * _.fill(array, 'a');
     * console.log(array);
     * // => ['a', 'a', 'a']
     *
     * _.fill(Array(3), 2);
     * // => [2, 2, 2]
     *
     * _.fill([4, 6, 8], '*', 1, 2);
     * // => [4, '*', 8]
     */
    function fill(array, value, start, end) {
      var length = array ? array.length : 0;
      if (!length) {
        return [];
      }
      if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
        start = 0;
        end = length;
      }
      return baseFill(array, value, start, end);
    }

    /**
     * This method is like `_.find` except that it returns the index of the first
     * element `predicate` returns truthy for instead of the element itself.
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to search.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {number} Returns the index of the found element, else `-1`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'active': false },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': true }
     * ];
     *
     * _.findIndex(users, function(chr) {
     *   return chr.user == 'barney';
     * });
     * // => 0
     *
     * // using the `_.matches` callback shorthand
     * _.findIndex(users, { 'user': 'fred', 'active': false });
     * // => 1
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.findIndex(users, 'active', false);
     * // => 0
     *
     * // using the `_.property` callback shorthand
     * _.findIndex(users, 'active');
     * // => 2
     */
    var findIndex = createFindIndex();

    /**
     * This method is like `_.findIndex` except that it iterates over elements
     * of `collection` from right to left.
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to search.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {number} Returns the index of the found element, else `-1`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'active': true },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': false }
     * ];
     *
     * _.findLastIndex(users, function(chr) {
     *   return chr.user == 'pebbles';
     * });
     * // => 2
     *
     * // using the `_.matches` callback shorthand
     * _.findLastIndex(users, { 'user': 'barney', 'active': true });
     * // => 0
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.findLastIndex(users, 'active', false);
     * // => 2
     *
     * // using the `_.property` callback shorthand
     * _.findLastIndex(users, 'active');
     * // => 0
     */
    var findLastIndex = createFindIndex(true);

    /**
     * Gets the first element of `array`.
     *
     * @static
     * @memberOf _
     * @alias head
     * @category Array
     * @param {Array} array The array to query.
     * @returns {*} Returns the first element of `array`.
     * @example
     *
     * _.first([1, 2, 3]);
     * // => 1
     *
     * _.first([]);
     * // => undefined
     */
    function first(array) {
      return array ? array[0] : undefined;
    }

    /**
     * Flattens a nested array. If `isDeep` is `true` the array is recursively
     * flattened, otherwise it is only flattened a single level.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to flatten.
     * @param {boolean} [isDeep] Specify a deep flatten.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Array} Returns the new flattened array.
     * @example
     *
     * _.flatten([1, [2, 3, [4]]]);
     * // => [1, 2, 3, [4]]
     *
     * // using `isDeep`
     * _.flatten([1, [2, 3, [4]]], true);
     * // => [1, 2, 3, 4]
     */
    function flatten(array, isDeep, guard) {
      var length = array ? array.length : 0;
      if (guard && isIterateeCall(array, isDeep, guard)) {
        isDeep = false;
      }
      return length ? baseFlatten(array, isDeep) : [];
    }

    /**
     * Recursively flattens a nested array.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to recursively flatten.
     * @returns {Array} Returns the new flattened array.
     * @example
     *
     * _.flattenDeep([1, [2, 3, [4]]]);
     * // => [1, 2, 3, 4]
     */
    function flattenDeep(array) {
      var length = array ? array.length : 0;
      return length ? baseFlatten(array, true) : [];
    }

    /**
     * Gets the index at which the first occurrence of `value` is found in `array`
     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
     * for equality comparisons. If `fromIndex` is negative, it is used as the offset
     * from the end of `array`. If `array` is sorted providing `true` for `fromIndex`
     * performs a faster binary search.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to search.
     * @param {*} value The value to search for.
     * @param {boolean|number} [fromIndex=0] The index to search from or `true`
     *  to perform a binary search on a sorted array.
     * @returns {number} Returns the index of the matched value, else `-1`.
     * @example
     *
     * _.indexOf([1, 2, 1, 2], 2);
     * // => 1
     *
     * // using `fromIndex`
     * _.indexOf([1, 2, 1, 2], 2, 2);
     * // => 3
     *
     * // performing a binary search
     * _.indexOf([1, 1, 2, 2], 2, true);
     * // => 2
     */
    function indexOf(array, value, fromIndex) {
      var length = array ? array.length : 0;
      if (!length) {
        return -1;
      }
      if (typeof fromIndex == 'number') {
        fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : fromIndex;
      } else if (fromIndex) {
        var index = binaryIndex(array, value);
        if (index < length &&
            (value === value ? (value === array[index]) : (array[index] !== array[index]))) {
          return index;
        }
        return -1;
      }
      return baseIndexOf(array, value, fromIndex || 0);
    }

    /**
     * Gets all but the last element of `array`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.initial([1, 2, 3]);
     * // => [1, 2]
     */
    function initial(array) {
      return dropRight(array, 1);
    }

    /**
     * Creates an array of unique values that are included in all of the provided
     * arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
     * for equality comparisons.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @returns {Array} Returns the new array of shared values.
     * @example
     * _.intersection([1, 2], [4, 2], [2, 1]);
     * // => [2]
     */
    var intersection = restParam(function(arrays) {
      var othLength = arrays.length,
          othIndex = othLength,
          caches = Array(length),
          indexOf = getIndexOf(),
          isCommon = indexOf == baseIndexOf,
          result = [];

      while (othIndex--) {
        var value = arrays[othIndex] = isArrayLike(value = arrays[othIndex]) ? value : [];
        caches[othIndex] = (isCommon && value.length >= 120) ? createCache(othIndex && value) : null;
      }
      var array = arrays[0],
          index = -1,
          length = array ? array.length : 0,
          seen = caches[0];

      outer:
      while (++index < length) {
        value = array[index];
        if ((seen ? cacheIndexOf(seen, value) : indexOf(result, value, 0)) < 0) {
          var othIndex = othLength;
          while (--othIndex) {
            var cache = caches[othIndex];
            if ((cache ? cacheIndexOf(cache, value) : indexOf(arrays[othIndex], value, 0)) < 0) {
              continue outer;
            }
          }
          if (seen) {
            seen.push(value);
          }
          result.push(value);
        }
      }
      return result;
    });

    /**
     * Gets the last element of `array`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @returns {*} Returns the last element of `array`.
     * @example
     *
     * _.last([1, 2, 3]);
     * // => 3
     */
    function last(array) {
      var length = array ? array.length : 0;
      return length ? array[length - 1] : undefined;
    }

    /**
     * This method is like `_.indexOf` except that it iterates over elements of
     * `array` from right to left.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to search.
     * @param {*} value The value to search for.
     * @param {boolean|number} [fromIndex=array.length-1] The index to search from
     *  or `true` to perform a binary search on a sorted array.
     * @returns {number} Returns the index of the matched value, else `-1`.
     * @example
     *
     * _.lastIndexOf([1, 2, 1, 2], 2);
     * // => 3
     *
     * // using `fromIndex`
     * _.lastIndexOf([1, 2, 1, 2], 2, 2);
     * // => 1
     *
     * // performing a binary search
     * _.lastIndexOf([1, 1, 2, 2], 2, true);
     * // => 3
     */
    function lastIndexOf(array, value, fromIndex) {
      var length = array ? array.length : 0;
      if (!length) {
        return -1;
      }
      var index = length;
      if (typeof fromIndex == 'number') {
        index = (fromIndex < 0 ? nativeMax(length + fromIndex, 0) : nativeMin(fromIndex || 0, length - 1)) + 1;
      } else if (fromIndex) {
        index = binaryIndex(array, value, true) - 1;
        var other = array[index];
        if (value === value ? (value === other) : (other !== other)) {
          return index;
        }
        return -1;
      }
      if (value !== value) {
        return indexOfNaN(array, index, true);
      }
      while (index--) {
        if (array[index] === value) {
          return index;
        }
      }
      return -1;
    }

    /**
     * Removes all provided values from `array` using
     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
     * for equality comparisons.
     *
     * **Note:** Unlike `_.without`, this method mutates `array`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to modify.
     * @param {...*} [values] The values to remove.
     * @returns {Array} Returns `array`.
     * @example
     *
     * var array = [1, 2, 3, 1, 2, 3];
     *
     * _.pull(array, 2, 3);
     * console.log(array);
     * // => [1, 1]
     */
    function pull() {
      var args = arguments,
          array = args[0];

      if (!(array && array.length)) {
        return array;
      }
      var index = 0,
          indexOf = getIndexOf(),
          length = args.length;

      while (++index < length) {
        var fromIndex = 0,
            value = args[index];

        while ((fromIndex = indexOf(array, value, fromIndex)) > -1) {
          splice.call(array, fromIndex, 1);
        }
      }
      return array;
    }

    /**
     * Removes elements from `array` corresponding to the given indexes and returns
     * an array of the removed elements. Indexes may be specified as an array of
     * indexes or as individual arguments.
     *
     * **Note:** Unlike `_.at`, this method mutates `array`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to modify.
     * @param {...(number|number[])} [indexes] The indexes of elements to remove,
     *  specified as individual indexes or arrays of indexes.
     * @returns {Array} Returns the new array of removed elements.
     * @example
     *
     * var array = [5, 10, 15, 20];
     * var evens = _.pullAt(array, 1, 3);
     *
     * console.log(array);
     * // => [5, 15]
     *
     * console.log(evens);
     * // => [10, 20]
     */
    var pullAt = restParam(function(array, indexes) {
      indexes = baseFlatten(indexes);

      var result = baseAt(array, indexes);
      basePullAt(array, indexes.sort(baseCompareAscending));
      return result;
    });

    /**
     * Removes all elements from `array` that `predicate` returns truthy for
     * and returns an array of the removed elements. The predicate is bound to
     * `thisArg` and invoked with three arguments: (value, index, array).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * **Note:** Unlike `_.filter`, this method mutates `array`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to modify.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Array} Returns the new array of removed elements.
     * @example
     *
     * var array = [1, 2, 3, 4];
     * var evens = _.remove(array, function(n) {
     *   return n % 2 == 0;
     * });
     *
     * console.log(array);
     * // => [1, 3]
     *
     * console.log(evens);
     * // => [2, 4]
     */
    function remove(array, predicate, thisArg) {
      var result = [];
      if (!(array && array.length)) {
        return result;
      }
      var index = -1,
          indexes = [],
          length = array.length;

      predicate = getCallback(predicate, thisArg, 3);
      while (++index < length) {
        var value = array[index];
        if (predicate(value, index, array)) {
          result.push(value);
          indexes.push(index);
        }
      }
      basePullAt(array, indexes);
      return result;
    }

    /**
     * Gets all but the first element of `array`.
     *
     * @static
     * @memberOf _
     * @alias tail
     * @category Array
     * @param {Array} array The array to query.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.rest([1, 2, 3]);
     * // => [2, 3]
     */
    function rest(array) {
      return drop(array, 1);
    }

    /**
     * Creates a slice of `array` from `start` up to, but not including, `end`.
     *
     * **Note:** This method is used instead of `Array#slice` to support node
     * lists in IE < 9 and to ensure dense arrays are returned.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to slice.
     * @param {number} [start=0] The start position.
     * @param {number} [end=array.length] The end position.
     * @returns {Array} Returns the slice of `array`.
     */
    function slice(array, start, end) {
      var length = array ? array.length : 0;
      if (!length) {
        return [];
      }
      if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
        start = 0;
        end = length;
      }
      return baseSlice(array, start, end);
    }

    /**
     * Uses a binary search to determine the lowest index at which `value` should
     * be inserted into `array` in order to maintain its sort order. If an iteratee
     * function is provided it is invoked for `value` and each element of `array`
     * to compute their sort ranking. The iteratee is bound to `thisArg` and
     * invoked with one argument; (value).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     * @example
     *
     * _.sortedIndex([30, 50], 40);
     * // => 1
     *
     * _.sortedIndex([4, 4, 5, 5], 5);
     * // => 2
     *
     * var dict = { 'data': { 'thirty': 30, 'forty': 40, 'fifty': 50 } };
     *
     * // using an iteratee function
     * _.sortedIndex(['thirty', 'fifty'], 'forty', function(word) {
     *   return this.data[word];
     * }, dict);
     * // => 1
     *
     * // using the `_.property` callback shorthand
     * _.sortedIndex([{ 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
     * // => 1
     */
    var sortedIndex = createSortedIndex();

    /**
     * This method is like `_.sortedIndex` except that it returns the highest
     * index at which `value` should be inserted into `array` in order to
     * maintain its sort order.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     * @example
     *
     * _.sortedLastIndex([4, 4, 5, 5], 5);
     * // => 4
     */
    var sortedLastIndex = createSortedIndex(true);

    /**
     * Creates a slice of `array` with `n` elements taken from the beginning.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @param {number} [n=1] The number of elements to take.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.take([1, 2, 3]);
     * // => [1]
     *
     * _.take([1, 2, 3], 2);
     * // => [1, 2]
     *
     * _.take([1, 2, 3], 5);
     * // => [1, 2, 3]
     *
     * _.take([1, 2, 3], 0);
     * // => []
     */
    function take(array, n, guard) {
      var length = array ? array.length : 0;
      if (!length) {
        return [];
      }
      if (guard ? isIterateeCall(array, n, guard) : n == null) {
        n = 1;
      }
      return baseSlice(array, 0, n < 0 ? 0 : n);
    }

    /**
     * Creates a slice of `array` with `n` elements taken from the end.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @param {number} [n=1] The number of elements to take.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.takeRight([1, 2, 3]);
     * // => [3]
     *
     * _.takeRight([1, 2, 3], 2);
     * // => [2, 3]
     *
     * _.takeRight([1, 2, 3], 5);
     * // => [1, 2, 3]
     *
     * _.takeRight([1, 2, 3], 0);
     * // => []
     */
    function takeRight(array, n, guard) {
      var length = array ? array.length : 0;
      if (!length) {
        return [];
      }
      if (guard ? isIterateeCall(array, n, guard) : n == null) {
        n = 1;
      }
      n = length - (+n || 0);
      return baseSlice(array, n < 0 ? 0 : n);
    }

    /**
     * Creates a slice of `array` with elements taken from the end. Elements are
     * taken until `predicate` returns falsey. The predicate is bound to `thisArg`
     * and invoked with three arguments: (value, index, array).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.takeRightWhile([1, 2, 3], function(n) {
     *   return n > 1;
     * });
     * // => [2, 3]
     *
     * var users = [
     *   { 'user': 'barney',  'active': true },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': false }
     * ];
     *
     * // using the `_.matches` callback shorthand
     * _.pluck(_.takeRightWhile(users, { 'user': 'pebbles', 'active': false }), 'user');
     * // => ['pebbles']
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.pluck(_.takeRightWhile(users, 'active', false), 'user');
     * // => ['fred', 'pebbles']
     *
     * // using the `_.property` callback shorthand
     * _.pluck(_.takeRightWhile(users, 'active'), 'user');
     * // => []
     */
    function takeRightWhile(array, predicate, thisArg) {
      return (array && array.length)
        ? baseWhile(array, getCallback(predicate, thisArg, 3), false, true)
        : [];
    }

    /**
     * Creates a slice of `array` with elements taken from the beginning. Elements
     * are taken until `predicate` returns falsey. The predicate is bound to
     * `thisArg` and invoked with three arguments: (value, index, array).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to query.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.takeWhile([1, 2, 3], function(n) {
     *   return n < 3;
     * });
     * // => [1, 2]
     *
     * var users = [
     *   { 'user': 'barney',  'active': false },
     *   { 'user': 'fred',    'active': false},
     *   { 'user': 'pebbles', 'active': true }
     * ];
     *
     * // using the `_.matches` callback shorthand
     * _.pluck(_.takeWhile(users, { 'user': 'barney', 'active': false }), 'user');
     * // => ['barney']
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.pluck(_.takeWhile(users, 'active', false), 'user');
     * // => ['barney', 'fred']
     *
     * // using the `_.property` callback shorthand
     * _.pluck(_.takeWhile(users, 'active'), 'user');
     * // => []
     */
    function takeWhile(array, predicate, thisArg) {
      return (array && array.length)
        ? baseWhile(array, getCallback(predicate, thisArg, 3))
        : [];
    }

    /**
     * Creates an array of unique values, in order, from all of the provided arrays
     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
     * for equality comparisons.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @returns {Array} Returns the new array of combined values.
     * @example
     *
     * _.union([1, 2], [4, 2], [2, 1]);
     * // => [1, 2, 4]
     */
    var union = restParam(function(arrays) {
      return baseUniq(baseFlatten(arrays, false, true));
    });

    /**
     * Creates a duplicate-free version of an array, using
     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
     * for equality comparisons, in which only the first occurence of each element
     * is kept. Providing `true` for `isSorted` performs a faster search algorithm
     * for sorted arrays. If an iteratee function is provided it is invoked for
     * each element in the array to generate the criterion by which uniqueness
     * is computed. The `iteratee` is bound to `thisArg` and invoked with three
     * arguments: (value, index, array).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @alias unique
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {boolean} [isSorted] Specify the array is sorted.
     * @param {Function|Object|string} [iteratee] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Array} Returns the new duplicate-value-free array.
     * @example
     *
     * _.uniq([2, 1, 2]);
     * // => [2, 1]
     *
     * // using `isSorted`
     * _.uniq([1, 1, 2], true);
     * // => [1, 2]
     *
     * // using an iteratee function
     * _.uniq([1, 2.5, 1.5, 2], function(n) {
     *   return this.floor(n);
     * }, Math);
     * // => [1, 2.5]
     *
     * // using the `_.property` callback shorthand
     * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
     * // => [{ 'x': 1 }, { 'x': 2 }]
     */
    function uniq(array, isSorted, iteratee, thisArg) {
      var length = array ? array.length : 0;
      if (!length) {
        return [];
      }
      if (isSorted != null && typeof isSorted != 'boolean') {
        thisArg = iteratee;
        iteratee = isIterateeCall(array, isSorted, thisArg) ? undefined : isSorted;
        isSorted = false;
      }
      var callback = getCallback();
      if (!(iteratee == null && callback === baseCallback)) {
        iteratee = callback(iteratee, thisArg, 3);
      }
      return (isSorted && getIndexOf() == baseIndexOf)
        ? sortedUniq(array, iteratee)
        : baseUniq(array, iteratee);
    }

    /**
     * This method is like `_.zip` except that it accepts an array of grouped
     * elements and creates an array regrouping the elements to their pre-zip
     * configuration.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array of grouped elements to process.
     * @returns {Array} Returns the new array of regrouped elements.
     * @example
     *
     * var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
     * // => [['fred', 30, true], ['barney', 40, false]]
     *
     * _.unzip(zipped);
     * // => [['fred', 'barney'], [30, 40], [true, false]]
     */
    function unzip(array) {
      if (!(array && array.length)) {
        return [];
      }
      var index = -1,
          length = 0;

      array = arrayFilter(array, function(group) {
        if (isArrayLike(group)) {
          length = nativeMax(group.length, length);
          return true;
        }
      });
      var result = Array(length);
      while (++index < length) {
        result[index] = arrayMap(array, baseProperty(index));
      }
      return result;
    }

    /**
     * This method is like `_.unzip` except that it accepts an iteratee to specify
     * how regrouped values should be combined. The `iteratee` is bound to `thisArg`
     * and invoked with four arguments: (accumulator, value, index, group).
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array of grouped elements to process.
     * @param {Function} [iteratee] The function to combine regrouped values.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Array} Returns the new array of regrouped elements.
     * @example
     *
     * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
     * // => [[1, 10, 100], [2, 20, 200]]
     *
     * _.unzipWith(zipped, _.add);
     * // => [3, 30, 300]
     */
    function unzipWith(array, iteratee, thisArg) {
      var length = array ? array.length : 0;
      if (!length) {
        return [];
      }
      var result = unzip(array);
      if (iteratee == null) {
        return result;
      }
      iteratee = bindCallback(iteratee, thisArg, 4);
      return arrayMap(result, function(group) {
        return arrayReduce(group, iteratee, undefined, true);
      });
    }

    /**
     * Creates an array excluding all provided values using
     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
     * for equality comparisons.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {Array} array The array to filter.
     * @param {...*} [values] The values to exclude.
     * @returns {Array} Returns the new array of filtered values.
     * @example
     *
     * _.without([1, 2, 1, 3], 1, 2);
     * // => [3]
     */
    var without = restParam(function(array, values) {
      return isArrayLike(array)
        ? baseDifference(array, values)
        : [];
    });

    /**
     * Creates an array of unique values that is the [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
     * of the provided arrays.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @returns {Array} Returns the new array of values.
     * @example
     *
     * _.xor([1, 2], [4, 2]);
     * // => [1, 4]
     */
    function xor() {
      var index = -1,
          length = arguments.length;

      while (++index < length) {
        var array = arguments[index];
        if (isArrayLike(array)) {
          var result = result
            ? arrayPush(baseDifference(result, array), baseDifference(array, result))
            : array;
        }
      }
      return result ? baseUniq(result) : [];
    }

    /**
     * Creates an array of grouped elements, the first of which contains the first
     * elements of the given arrays, the second of which contains the second elements
     * of the given arrays, and so on.
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {...Array} [arrays] The arrays to process.
     * @returns {Array} Returns the new array of grouped elements.
     * @example
     *
     * _.zip(['fred', 'barney'], [30, 40], [true, false]);
     * // => [['fred', 30, true], ['barney', 40, false]]
     */
    var zip = restParam(unzip);

    /**
     * The inverse of `_.pairs`; this method returns an object composed from arrays
     * of property names and values. Provide either a single two dimensional array,
     * e.g. `[[key1, value1], [key2, value2]]` or two arrays, one of property names
     * and one of corresponding values.
     *
     * @static
     * @memberOf _
     * @alias object
     * @category Array
     * @param {Array} props The property names.
     * @param {Array} [values=[]] The property values.
     * @returns {Object} Returns the new object.
     * @example
     *
     * _.zipObject([['fred', 30], ['barney', 40]]);
     * // => { 'fred': 30, 'barney': 40 }
     *
     * _.zipObject(['fred', 'barney'], [30, 40]);
     * // => { 'fred': 30, 'barney': 40 }
     */
    function zipObject(props, values) {
      var index = -1,
          length = props ? props.length : 0,
          result = {};

      if (length && !values && !isArray(props[0])) {
        values = [];
      }
      while (++index < length) {
        var key = props[index];
        if (values) {
          result[key] = values[index];
        } else if (key) {
          result[key[0]] = key[1];
        }
      }
      return result;
    }

    /**
     * This method is like `_.zip` except that it accepts an iteratee to specify
     * how grouped values should be combined. The `iteratee` is bound to `thisArg`
     * and invoked with four arguments: (accumulator, value, index, group).
     *
     * @static
     * @memberOf _
     * @category Array
     * @param {...Array} [arrays] The arrays to process.
     * @param {Function} [iteratee] The function to combine grouped values.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Array} Returns the new array of grouped elements.
     * @example
     *
     * _.zipWith([1, 2], [10, 20], [100, 200], _.add);
     * // => [111, 222]
     */
    var zipWith = restParam(function(arrays) {
      var length = arrays.length,
          iteratee = length > 2 ? arrays[length - 2] : undefined,
          thisArg = length > 1 ? arrays[length - 1] : undefined;

      if (length > 2 && typeof iteratee == 'function') {
        length -= 2;
      } else {
        iteratee = (length > 1 && typeof thisArg == 'function') ? (--length, thisArg) : undefined;
        thisArg = undefined;
      }
      arrays.length = length;
      return unzipWith(arrays, iteratee, thisArg);
    });

    /*------------------------------------------------------------------------*/

    /**
     * Creates a `lodash` object that wraps `value` with explicit method
     * chaining enabled.
     *
     * @static
     * @memberOf _
     * @category Chain
     * @param {*} value The value to wrap.
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'age': 36 },
     *   { 'user': 'fred',    'age': 40 },
     *   { 'user': 'pebbles', 'age': 1 }
     * ];
     *
     * var youngest = _.chain(users)
     *   .sortBy('age')
     *   .map(function(chr) {
     *     return chr.user + ' is ' + chr.age;
     *   })
     *   .first()
     *   .value();
     * // => 'pebbles is 1'
     */
    function chain(value) {
      var result = lodash(value);
      result.__chain__ = true;
      return result;
    }

    /**
     * This method invokes `interceptor` and returns `value`. The interceptor is
     * bound to `thisArg` and invoked with one argument; (value). The purpose of
     * this method is to "tap into" a method chain in order to perform operations
     * on intermediate results within the chain.
     *
     * @static
     * @memberOf _
     * @category Chain
     * @param {*} value The value to provide to `interceptor`.
     * @param {Function} interceptor The function to invoke.
     * @param {*} [thisArg] The `this` binding of `interceptor`.
     * @returns {*} Returns `value`.
     * @example
     *
     * _([1, 2, 3])
     *  .tap(function(array) {
     *    array.pop();
     *  })
     *  .reverse()
     *  .value();
     * // => [2, 1]
     */
    function tap(value, interceptor, thisArg) {
      interceptor.call(thisArg, value);
      return value;
    }

    /**
     * This method is like `_.tap` except that it returns the result of `interceptor`.
     *
     * @static
     * @memberOf _
     * @category Chain
     * @param {*} value The value to provide to `interceptor`.
     * @param {Function} interceptor The function to invoke.
     * @param {*} [thisArg] The `this` binding of `interceptor`.
     * @returns {*} Returns the result of `interceptor`.
     * @example
     *
     * _('  abc  ')
     *  .chain()
     *  .trim()
     *  .thru(function(value) {
     *    return [value];
     *  })
     *  .value();
     * // => ['abc']
     */
    function thru(value, interceptor, thisArg) {
      return interceptor.call(thisArg, value);
    }

    /**
     * Enables explicit method chaining on the wrapper object.
     *
     * @name chain
     * @memberOf _
     * @category Chain
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36 },
     *   { 'user': 'fred',   'age': 40 }
     * ];
     *
     * // without explicit chaining
     * _(users).first();
     * // => { 'user': 'barney', 'age': 36 }
     *
     * // with explicit chaining
     * _(users).chain()
     *   .first()
     *   .pick('user')
     *   .value();
     * // => { 'user': 'barney' }
     */
    function wrapperChain() {
      return chain(this);
    }

    /**
     * Executes the chained sequence and returns the wrapped result.
     *
     * @name commit
     * @memberOf _
     * @category Chain
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var array = [1, 2];
     * var wrapped = _(array).push(3);
     *
     * console.log(array);
     * // => [1, 2]
     *
     * wrapped = wrapped.commit();
     * console.log(array);
     * // => [1, 2, 3]
     *
     * wrapped.last();
     * // => 3
     *
     * console.log(array);
     * // => [1, 2, 3]
     */
    function wrapperCommit() {
      return new LodashWrapper(this.value(), this.__chain__);
    }

    /**
     * Creates a new array joining a wrapped array with any additional arrays
     * and/or values.
     *
     * @name concat
     * @memberOf _
     * @category Chain
     * @param {...*} [values] The values to concatenate.
     * @returns {Array} Returns the new concatenated array.
     * @example
     *
     * var array = [1];
     * var wrapped = _(array).concat(2, [3], [[4]]);
     *
     * console.log(wrapped.value());
     * // => [1, 2, 3, [4]]
     *
     * console.log(array);
     * // => [1]
     */
    var wrapperConcat = restParam(function(values) {
      values = baseFlatten(values);
      return this.thru(function(array) {
        return arrayConcat(isArray(array) ? array : [toObject(array)], values);
      });
    });

    /**
     * Creates a clone of the chained sequence planting `value` as the wrapped value.
     *
     * @name plant
     * @memberOf _
     * @category Chain
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var array = [1, 2];
     * var wrapped = _(array).map(function(value) {
     *   return Math.pow(value, 2);
     * });
     *
     * var other = [3, 4];
     * var otherWrapped = wrapped.plant(other);
     *
     * otherWrapped.value();
     * // => [9, 16]
     *
     * wrapped.value();
     * // => [1, 4]
     */
    function wrapperPlant(value) {
      var result,
          parent = this;

      while (parent instanceof baseLodash) {
        var clone = wrapperClone(parent);
        if (result) {
          previous.__wrapped__ = clone;
        } else {
          result = clone;
        }
        var previous = clone;
        parent = parent.__wrapped__;
      }
      previous.__wrapped__ = value;
      return result;
    }

    /**
     * Reverses the wrapped array so the first element becomes the last, the
     * second element becomes the second to last, and so on.
     *
     * **Note:** This method mutates the wrapped array.
     *
     * @name reverse
     * @memberOf _
     * @category Chain
     * @returns {Object} Returns the new reversed `lodash` wrapper instance.
     * @example
     *
     * var array = [1, 2, 3];
     *
     * _(array).reverse().value()
     * // => [3, 2, 1]
     *
     * console.log(array);
     * // => [3, 2, 1]
     */
    function wrapperReverse() {
      var value = this.__wrapped__;

      var interceptor = function(value) {
        return (wrapped && wrapped.__dir__ < 0) ? value : value.reverse();
      };
      if (value instanceof LazyWrapper) {
        var wrapped = value;
        if (this.__actions__.length) {
          wrapped = new LazyWrapper(this);
        }
        wrapped = wrapped.reverse();
        wrapped.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
        return new LodashWrapper(wrapped, this.__chain__);
      }
      return this.thru(interceptor);
    }

    /**
     * Produces the result of coercing the unwrapped value to a string.
     *
     * @name toString
     * @memberOf _
     * @category Chain
     * @returns {string} Returns the coerced string value.
     * @example
     *
     * _([1, 2, 3]).toString();
     * // => '1,2,3'
     */
    function wrapperToString() {
      return (this.value() + '');
    }

    /**
     * Executes the chained sequence to extract the unwrapped value.
     *
     * @name value
     * @memberOf _
     * @alias run, toJSON, valueOf
     * @category Chain
     * @returns {*} Returns the resolved unwrapped value.
     * @example
     *
     * _([1, 2, 3]).value();
     * // => [1, 2, 3]
     */
    function wrapperValue() {
      return baseWrapperValue(this.__wrapped__, this.__actions__);
    }

    /*------------------------------------------------------------------------*/

    /**
     * Creates an array of elements corresponding to the given keys, or indexes,
     * of `collection`. Keys may be specified as individual arguments or as arrays
     * of keys.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {...(number|number[]|string|string[])} [props] The property names
     *  or indexes of elements to pick, specified individually or in arrays.
     * @returns {Array} Returns the new array of picked elements.
     * @example
     *
     * _.at(['a', 'b', 'c'], [0, 2]);
     * // => ['a', 'c']
     *
     * _.at(['barney', 'fred', 'pebbles'], 0, 2);
     * // => ['barney', 'pebbles']
     */
    var at = restParam(function(collection, props) {
      return baseAt(collection, baseFlatten(props));
    });

    /**
     * Creates an object composed of keys generated from the results of running
     * each element of `collection` through `iteratee`. The corresponding value
     * of each key is the number of times the key was returned by `iteratee`.
     * The `iteratee` is bound to `thisArg` and invoked with three arguments:
     * (value, index|key, collection).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Object} Returns the composed aggregate object.
     * @example
     *
     * _.countBy([4.3, 6.1, 6.4], function(n) {
     *   return Math.floor(n);
     * });
     * // => { '4': 1, '6': 2 }
     *
     * _.countBy([4.3, 6.1, 6.4], function(n) {
     *   return this.floor(n);
     * }, Math);
     * // => { '4': 1, '6': 2 }
     *
     * _.countBy(['one', 'two', 'three'], 'length');
     * // => { '3': 2, '5': 1 }
     */
    var countBy = createAggregator(function(result, value, key) {
      hasOwnProperty.call(result, key) ? ++result[key] : (result[key] = 1);
    });

    /**
     * Checks if `predicate` returns truthy for **all** elements of `collection`.
     * The predicate is bound to `thisArg` and invoked with three arguments:
     * (value, index|key, collection).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @alias all
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {boolean} Returns `true` if all elements pass the predicate check,
     *  else `false`.
     * @example
     *
     * _.every([true, 1, null, 'yes'], Boolean);
     * // => false
     *
     * var users = [
     *   { 'user': 'barney', 'active': false },
     *   { 'user': 'fred',   'active': false }
     * ];
     *
     * // using the `_.matches` callback shorthand
     * _.every(users, { 'user': 'barney', 'active': false });
     * // => false
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.every(users, 'active', false);
     * // => true
     *
     * // using the `_.property` callback shorthand
     * _.every(users, 'active');
     * // => false
     */
    function every(collection, predicate, thisArg) {
      var func = isArray(collection) ? arrayEvery : baseEvery;
      if (thisArg && isIterateeCall(collection, predicate, thisArg)) {
        predicate = undefined;
      }
      if (typeof predicate != 'function' || thisArg !== undefined) {
        predicate = getCallback(predicate, thisArg, 3);
      }
      return func(collection, predicate);
    }

    /**
     * Iterates over elements of `collection`, returning an array of all elements
     * `predicate` returns truthy for. The predicate is bound to `thisArg` and
     * invoked with three arguments: (value, index|key, collection).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @alias select
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Array} Returns the new filtered array.
     * @example
     *
     * _.filter([4, 5, 6], function(n) {
     *   return n % 2 == 0;
     * });
     * // => [4, 6]
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36, 'active': true },
     *   { 'user': 'fred',   'age': 40, 'active': false }
     * ];
     *
     * // using the `_.matches` callback shorthand
     * _.pluck(_.filter(users, { 'age': 36, 'active': true }), 'user');
     * // => ['barney']
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.pluck(_.filter(users, 'active', false), 'user');
     * // => ['fred']
     *
     * // using the `_.property` callback shorthand
     * _.pluck(_.filter(users, 'active'), 'user');
     * // => ['barney']
     */
    function filter(collection, predicate, thisArg) {
      var func = isArray(collection) ? arrayFilter : baseFilter;
      predicate = getCallback(predicate, thisArg, 3);
      return func(collection, predicate);
    }

    /**
     * Iterates over elements of `collection`, returning the first element
     * `predicate` returns truthy for. The predicate is bound to `thisArg` and
     * invoked with three arguments: (value, index|key, collection).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @alias detect
     * @category Collection
     * @param {Array|Object|string} collection The collection to search.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {*} Returns the matched element, else `undefined`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'age': 36, 'active': true },
     *   { 'user': 'fred',    'age': 40, 'active': false },
     *   { 'user': 'pebbles', 'age': 1,  'active': true }
     * ];
     *
     * _.result(_.find(users, function(chr) {
     *   return chr.age < 40;
     * }), 'user');
     * // => 'barney'
     *
     * // using the `_.matches` callback shorthand
     * _.result(_.find(users, { 'age': 1, 'active': true }), 'user');
     * // => 'pebbles'
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.result(_.find(users, 'active', false), 'user');
     * // => 'fred'
     *
     * // using the `_.property` callback shorthand
     * _.result(_.find(users, 'active'), 'user');
     * // => 'barney'
     */
    var find = createFind(baseEach);

    /**
     * This method is like `_.find` except that it iterates over elements of
     * `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to search.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {*} Returns the matched element, else `undefined`.
     * @example
     *
     * _.findLast([1, 2, 3, 4], function(n) {
     *   return n % 2 == 1;
     * });
     * // => 3
     */
    var findLast = createFind(baseEachRight, true);

    /**
     * Performs a deep comparison between each element in `collection` and the
     * source object, returning the first element that has equivalent property
     * values.
     *
     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
     * numbers, `Object` objects, regexes, and strings. Objects are compared by
     * their own, not inherited, enumerable properties. For comparing a single
     * own or inherited property value see `_.matchesProperty`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to search.
     * @param {Object} source The object of property values to match.
     * @returns {*} Returns the matched element, else `undefined`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36, 'active': true },
     *   { 'user': 'fred',   'age': 40, 'active': false }
     * ];
     *
     * _.result(_.findWhere(users, { 'age': 36, 'active': true }), 'user');
     * // => 'barney'
     *
     * _.result(_.findWhere(users, { 'age': 40, 'active': false }), 'user');
     * // => 'fred'
     */
    function findWhere(collection, source) {
      return find(collection, baseMatches(source));
    }

    /**
     * Iterates over elements of `collection` invoking `iteratee` for each element.
     * The `iteratee` is bound to `thisArg` and invoked with three arguments:
     * (value, index|key, collection). Iteratee functions may exit iteration early
     * by explicitly returning `false`.
     *
     * **Note:** As with other "Collections" methods, objects with a "length" property
     * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
     * may be used for object iteration.
     *
     * @static
     * @memberOf _
     * @alias each
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Array|Object|string} Returns `collection`.
     * @example
     *
     * _([1, 2]).forEach(function(n) {
     *   console.log(n);
     * }).value();
     * // => logs each value from left to right and returns the array
     *
     * _.forEach({ 'a': 1, 'b': 2 }, function(n, key) {
     *   console.log(n, key);
     * });
     * // => logs each value-key pair and returns the object (iteration order is not guaranteed)
     */
    var forEach = createForEach(arrayEach, baseEach);

    /**
     * This method is like `_.forEach` except that it iterates over elements of
     * `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @alias eachRight
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Array|Object|string} Returns `collection`.
     * @example
     *
     * _([1, 2]).forEachRight(function(n) {
     *   console.log(n);
     * }).value();
     * // => logs each value from right to left and returns the array
     */
    var forEachRight = createForEach(arrayEachRight, baseEachRight);

    /**
     * Creates an object composed of keys generated from the results of running
     * each element of `collection` through `iteratee`. The corresponding value
     * of each key is an array of the elements responsible for generating the key.
     * The `iteratee` is bound to `thisArg` and invoked with three arguments:
     * (value, index|key, collection).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Object} Returns the composed aggregate object.
     * @example
     *
     * _.groupBy([4.2, 6.1, 6.4], function(n) {
     *   return Math.floor(n);
     * });
     * // => { '4': [4.2], '6': [6.1, 6.4] }
     *
     * _.groupBy([4.2, 6.1, 6.4], function(n) {
     *   return this.floor(n);
     * }, Math);
     * // => { '4': [4.2], '6': [6.1, 6.4] }
     *
     * // using the `_.property` callback shorthand
     * _.groupBy(['one', 'two', 'three'], 'length');
     * // => { '3': ['one', 'two'], '5': ['three'] }
     */
    var groupBy = createAggregator(function(result, value, key) {
      if (hasOwnProperty.call(result, key)) {
        result[key].push(value);
      } else {
        result[key] = [value];
      }
    });

    /**
     * Checks if `value` is in `collection` using
     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
     * for equality comparisons. If `fromIndex` is negative, it is used as the offset
     * from the end of `collection`.
     *
     * @static
     * @memberOf _
     * @alias contains, include
     * @category Collection
     * @param {Array|Object|string} collection The collection to search.
     * @param {*} target The value to search for.
     * @param {number} [fromIndex=0] The index to search from.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.reduce`.
     * @returns {boolean} Returns `true` if a matching element is found, else `false`.
     * @example
     *
     * _.includes([1, 2, 3], 1);
     * // => true
     *
     * _.includes([1, 2, 3], 1, 2);
     * // => false
     *
     * _.includes({ 'user': 'fred', 'age': 40 }, 'fred');
     * // => true
     *
     * _.includes('pebbles', 'eb');
     * // => true
     */
    function includes(collection, target, fromIndex, guard) {
      var length = collection ? getLength(collection) : 0;
      if (!isLength(length)) {
        collection = values(collection);
        length = collection.length;
      }
      if (typeof fromIndex != 'number' || (guard && isIterateeCall(target, fromIndex, guard))) {
        fromIndex = 0;
      } else {
        fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0);
      }
      return (typeof collection == 'string' || !isArray(collection) && isString(collection))
        ? (fromIndex <= length && collection.indexOf(target, fromIndex) > -1)
        : (!!length && getIndexOf(collection, target, fromIndex) > -1);
    }

    /**
     * Creates an object composed of keys generated from the results of running
     * each element of `collection` through `iteratee`. The corresponding value
     * of each key is the last element responsible for generating the key. The
     * iteratee function is bound to `thisArg` and invoked with three arguments:
     * (value, index|key, collection).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Object} Returns the composed aggregate object.
     * @example
     *
     * var keyData = [
     *   { 'dir': 'left', 'code': 97 },
     *   { 'dir': 'right', 'code': 100 }
     * ];
     *
     * _.indexBy(keyData, 'dir');
     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
     *
     * _.indexBy(keyData, function(object) {
     *   return String.fromCharCode(object.code);
     * });
     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
     *
     * _.indexBy(keyData, function(object) {
     *   return this.fromCharCode(object.code);
     * }, String);
     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
     */
    var indexBy = createAggregator(function(result, value, key) {
      result[key] = value;
    });

    /**
     * Invokes the method at `path` of each element in `collection`, returning
     * an array of the results of each invoked method. Any additional arguments
     * are provided to each invoked method. If `methodName` is a function it is
     * invoked for, and `this` bound to, each element in `collection`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Array|Function|string} path The path of the method to invoke or
     *  the function invoked per iteration.
     * @param {...*} [args] The arguments to invoke the method with.
     * @returns {Array} Returns the array of results.
     * @example
     *
     * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
     * // => [[1, 5, 7], [1, 2, 3]]
     *
     * _.invoke([123, 456], String.prototype.split, '');
     * // => [['1', '2', '3'], ['4', '5', '6']]
     */
    var invoke = restParam(function(collection, path, args) {
      var index = -1,
          isFunc = typeof path == 'function',
          isProp = isKey(path),
          result = isArrayLike(collection) ? Array(collection.length) : [];

      baseEach(collection, function(value) {
        var func = isFunc ? path : ((isProp && value != null) ? value[path] : undefined);
        result[++index] = func ? func.apply(value, args) : invokePath(value, path, args);
      });
      return result;
    });

    /**
     * Creates an array of values by running each element in `collection` through
     * `iteratee`. The `iteratee` is bound to `thisArg` and invoked with three
     * arguments: (value, index|key, collection).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * Many lodash methods are guarded to work as iteratees for methods like
     * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
     *
     * The guarded methods are:
     * `ary`, `callback`, `chunk`, `clone`, `create`, `curry`, `curryRight`,
     * `drop`, `dropRight`, `every`, `fill`, `flatten`, `invert`, `max`, `min`,
     * `parseInt`, `slice`, `sortBy`, `take`, `takeRight`, `template`, `trim`,
     * `trimLeft`, `trimRight`, `trunc`, `random`, `range`, `sample`, `some`,
     * `sum`, `uniq`, and `words`
     *
     * @static
     * @memberOf _
     * @alias collect
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Array} Returns the new mapped array.
     * @example
     *
     * function timesThree(n) {
     *   return n * 3;
     * }
     *
     * _.map([1, 2], timesThree);
     * // => [3, 6]
     *
     * _.map({ 'a': 1, 'b': 2 }, timesThree);
     * // => [3, 6] (iteration order is not guaranteed)
     *
     * var users = [
     *   { 'user': 'barney' },
     *   { 'user': 'fred' }
     * ];
     *
     * // using the `_.property` callback shorthand
     * _.map(users, 'user');
     * // => ['barney', 'fred']
     */
    function map(collection, iteratee, thisArg) {
      var func = isArray(collection) ? arrayMap : baseMap;
      iteratee = getCallback(iteratee, thisArg, 3);
      return func(collection, iteratee);
    }

    /**
     * Creates an array of elements split into two groups, the first of which
     * contains elements `predicate` returns truthy for, while the second of which
     * contains elements `predicate` returns falsey for. The predicate is bound
     * to `thisArg` and invoked with three arguments: (value, index|key, collection).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Array} Returns the array of grouped elements.
     * @example
     *
     * _.partition([1, 2, 3], function(n) {
     *   return n % 2;
     * });
     * // => [[1, 3], [2]]
     *
     * _.partition([1.2, 2.3, 3.4], function(n) {
     *   return this.floor(n) % 2;
     * }, Math);
     * // => [[1.2, 3.4], [2.3]]
     *
     * var users = [
     *   { 'user': 'barney',  'age': 36, 'active': false },
     *   { 'user': 'fred',    'age': 40, 'active': true },
     *   { 'user': 'pebbles', 'age': 1,  'active': false }
     * ];
     *
     * var mapper = function(array) {
     *   return _.pluck(array, 'user');
     * };
     *
     * // using the `_.matches` callback shorthand
     * _.map(_.partition(users, { 'age': 1, 'active': false }), mapper);
     * // => [['pebbles'], ['barney', 'fred']]
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.map(_.partition(users, 'active', false), mapper);
     * // => [['barney', 'pebbles'], ['fred']]
     *
     * // using the `_.property` callback shorthand
     * _.map(_.partition(users, 'active'), mapper);
     * // => [['fred'], ['barney', 'pebbles']]
     */
    var partition = createAggregator(function(result, value, key) {
      result[key ? 0 : 1].push(value);
    }, function() { return [[], []]; });

    /**
     * Gets the property value of `path` from all elements in `collection`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Array|string} path The path of the property to pluck.
     * @returns {Array} Returns the property values.
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36 },
     *   { 'user': 'fred',   'age': 40 }
     * ];
     *
     * _.pluck(users, 'user');
     * // => ['barney', 'fred']
     *
     * var userIndex = _.indexBy(users, 'user');
     * _.pluck(userIndex, 'age');
     * // => [36, 40] (iteration order is not guaranteed)
     */
    function pluck(collection, path) {
      return map(collection, property(path));
    }

    /**
     * Reduces `collection` to a value which is the accumulated result of running
     * each element in `collection` through `iteratee`, where each successive
     * invocation is supplied the return value of the previous. If `accumulator`
     * is not provided the first element of `collection` is used as the initial
     * value. The `iteratee` is bound to `thisArg` and invoked with four arguments:
     * (accumulator, value, index|key, collection).
     *
     * Many lodash methods are guarded to work as iteratees for methods like
     * `_.reduce`, `_.reduceRight`, and `_.transform`.
     *
     * The guarded methods are:
     * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `sortByAll`,
     * and `sortByOrder`
     *
     * @static
     * @memberOf _
     * @alias foldl, inject
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [accumulator] The initial value.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {*} Returns the accumulated value.
     * @example
     *
     * _.reduce([1, 2], function(total, n) {
     *   return total + n;
     * });
     * // => 3
     *
     * _.reduce({ 'a': 1, 'b': 2 }, function(result, n, key) {
     *   result[key] = n * 3;
     *   return result;
     * }, {});
     * // => { 'a': 3, 'b': 6 } (iteration order is not guaranteed)
     */
    var reduce = createReduce(arrayReduce, baseEach);

    /**
     * This method is like `_.reduce` except that it iterates over elements of
     * `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @alias foldr
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [accumulator] The initial value.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {*} Returns the accumulated value.
     * @example
     *
     * var array = [[0, 1], [2, 3], [4, 5]];
     *
     * _.reduceRight(array, function(flattened, other) {
     *   return flattened.concat(other);
     * }, []);
     * // => [4, 5, 2, 3, 0, 1]
     */
    var reduceRight = createReduce(arrayReduceRight, baseEachRight);

    /**
     * The opposite of `_.filter`; this method returns the elements of `collection`
     * that `predicate` does **not** return truthy for.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Array} Returns the new filtered array.
     * @example
     *
     * _.reject([1, 2, 3, 4], function(n) {
     *   return n % 2 == 0;
     * });
     * // => [1, 3]
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36, 'active': false },
     *   { 'user': 'fred',   'age': 40, 'active': true }
     * ];
     *
     * // using the `_.matches` callback shorthand
     * _.pluck(_.reject(users, { 'age': 40, 'active': true }), 'user');
     * // => ['barney']
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.pluck(_.reject(users, 'active', false), 'user');
     * // => ['fred']
     *
     * // using the `_.property` callback shorthand
     * _.pluck(_.reject(users, 'active'), 'user');
     * // => ['barney']
     */
    function reject(collection, predicate, thisArg) {
      var func = isArray(collection) ? arrayFilter : baseFilter;
      predicate = getCallback(predicate, thisArg, 3);
      return func(collection, function(value, index, collection) {
        return !predicate(value, index, collection);
      });
    }

    /**
     * Gets a random element or `n` random elements from a collection.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to sample.
     * @param {number} [n] The number of elements to sample.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {*} Returns the random sample(s).
     * @example
     *
     * _.sample([1, 2, 3, 4]);
     * // => 2
     *
     * _.sample([1, 2, 3, 4], 2);
     * // => [3, 1]
     */
    function sample(collection, n, guard) {
      if (guard ? isIterateeCall(collection, n, guard) : n == null) {
        collection = toIterable(collection);
        var length = collection.length;
        return length > 0 ? collection[baseRandom(0, length - 1)] : undefined;
      }
      var index = -1,
          result = toArray(collection),
          length = result.length,
          lastIndex = length - 1;

      n = nativeMin(n < 0 ? 0 : (+n || 0), length);
      while (++index < n) {
        var rand = baseRandom(index, lastIndex),
            value = result[rand];

        result[rand] = result[index];
        result[index] = value;
      }
      result.length = n;
      return result;
    }

    /**
     * Creates an array of shuffled values, using a version of the
     * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to shuffle.
     * @returns {Array} Returns the new shuffled array.
     * @example
     *
     * _.shuffle([1, 2, 3, 4]);
     * // => [4, 1, 3, 2]
     */
    function shuffle(collection) {
      return sample(collection, POSITIVE_INFINITY);
    }

    /**
     * Gets the size of `collection` by returning its length for array-like
     * values or the number of own enumerable properties for objects.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to inspect.
     * @returns {number} Returns the size of `collection`.
     * @example
     *
     * _.size([1, 2, 3]);
     * // => 3
     *
     * _.size({ 'a': 1, 'b': 2 });
     * // => 2
     *
     * _.size('pebbles');
     * // => 7
     */
    function size(collection) {
      var length = collection ? getLength(collection) : 0;
      return isLength(length) ? length : keys(collection).length;
    }

    /**
     * Checks if `predicate` returns truthy for **any** element of `collection`.
     * The function returns as soon as it finds a passing value and does not iterate
     * over the entire collection. The predicate is bound to `thisArg` and invoked
     * with three arguments: (value, index|key, collection).
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @alias any
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {boolean} Returns `true` if any element passes the predicate check,
     *  else `false`.
     * @example
     *
     * _.some([null, 0, 'yes', false], Boolean);
     * // => true
     *
     * var users = [
     *   { 'user': 'barney', 'active': true },
     *   { 'user': 'fred',   'active': false }
     * ];
     *
     * // using the `_.matches` callback shorthand
     * _.some(users, { 'user': 'barney', 'active': false });
     * // => false
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.some(users, 'active', false);
     * // => true
     *
     * // using the `_.property` callback shorthand
     * _.some(users, 'active');
     * // => true
     */
    function some(collection, predicate, thisArg) {
      var func = isArray(collection) ? arraySome : baseSome;
      if (thisArg && isIterateeCall(collection, predicate, thisArg)) {
        predicate = undefined;
      }
      if (typeof predicate != 'function' || thisArg !== undefined) {
        predicate = getCallback(predicate, thisArg, 3);
      }
      return func(collection, predicate);
    }

    /**
     * Creates an array of elements, sorted in ascending order by the results of
     * running each element in a collection through `iteratee`. This method performs
     * a stable sort, that is, it preserves the original sort order of equal elements.
     * The `iteratee` is bound to `thisArg` and invoked with three arguments:
     * (value, index|key, collection).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Array} Returns the new sorted array.
     * @example
     *
     * _.sortBy([1, 2, 3], function(n) {
     *   return Math.sin(n);
     * });
     * // => [3, 1, 2]
     *
     * _.sortBy([1, 2, 3], function(n) {
     *   return this.sin(n);
     * }, Math);
     * // => [3, 1, 2]
     *
     * var users = [
     *   { 'user': 'fred' },
     *   { 'user': 'pebbles' },
     *   { 'user': 'barney' }
     * ];
     *
     * // using the `_.property` callback shorthand
     * _.pluck(_.sortBy(users, 'user'), 'user');
     * // => ['barney', 'fred', 'pebbles']
     */
    function sortBy(collection, iteratee, thisArg) {
      if (collection == null) {
        return [];
      }
      if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
        iteratee = undefined;
      }
      var index = -1;
      iteratee = getCallback(iteratee, thisArg, 3);

      var result = baseMap(collection, function(value, key, collection) {
        return { 'criteria': iteratee(value, key, collection), 'index': ++index, 'value': value };
      });
      return baseSortBy(result, compareAscending);
    }

    /**
     * This method is like `_.sortBy` except that it can sort by multiple iteratees
     * or property names.
     *
     * If a property name is provided for an iteratee the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If an object is provided for an iteratee the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {...(Function|Function[]|Object|Object[]|string|string[])} iteratees
     *  The iteratees to sort by, specified as individual values or arrays of values.
     * @returns {Array} Returns the new sorted array.
     * @example
     *
     * var users = [
     *   { 'user': 'fred',   'age': 48 },
     *   { 'user': 'barney', 'age': 36 },
     *   { 'user': 'fred',   'age': 42 },
     *   { 'user': 'barney', 'age': 34 }
     * ];
     *
     * _.map(_.sortByAll(users, ['user', 'age']), _.values);
     * // => [['barney', 34], ['barney', 36], ['fred', 42], ['fred', 48]]
     *
     * _.map(_.sortByAll(users, 'user', function(chr) {
     *   return Math.floor(chr.age / 10);
     * }), _.values);
     * // => [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]
     */
    var sortByAll = restParam(function(collection, iteratees) {
      if (collection == null) {
        return [];
      }
      var guard = iteratees[2];
      if (guard && isIterateeCall(iteratees[0], iteratees[1], guard)) {
        iteratees.length = 1;
      }
      return baseSortByOrder(collection, baseFlatten(iteratees), []);
    });

    /**
     * This method is like `_.sortByAll` except that it allows specifying the
     * sort orders of the iteratees to sort by. If `orders` is unspecified, all
     * values are sorted in ascending order. Otherwise, a value is sorted in
     * ascending order if its corresponding order is "asc", and descending if "desc".
     *
     * If a property name is provided for an iteratee the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If an object is provided for an iteratee the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
     * @param {boolean[]} [orders] The sort orders of `iteratees`.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.reduce`.
     * @returns {Array} Returns the new sorted array.
     * @example
     *
     * var users = [
     *   { 'user': 'fred',   'age': 48 },
     *   { 'user': 'barney', 'age': 34 },
     *   { 'user': 'fred',   'age': 42 },
     *   { 'user': 'barney', 'age': 36 }
     * ];
     *
     * // sort by `user` in ascending order and by `age` in descending order
     * _.map(_.sortByOrder(users, ['user', 'age'], ['asc', 'desc']), _.values);
     * // => [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]
     */
    function sortByOrder(collection, iteratees, orders, guard) {
      if (collection == null) {
        return [];
      }
      if (guard && isIterateeCall(iteratees, orders, guard)) {
        orders = undefined;
      }
      if (!isArray(iteratees)) {
        iteratees = iteratees == null ? [] : [iteratees];
      }
      if (!isArray(orders)) {
        orders = orders == null ? [] : [orders];
      }
      return baseSortByOrder(collection, iteratees, orders);
    }

    /**
     * Performs a deep comparison between each element in `collection` and the
     * source object, returning an array of all elements that have equivalent
     * property values.
     *
     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
     * numbers, `Object` objects, regexes, and strings. Objects are compared by
     * their own, not inherited, enumerable properties. For comparing a single
     * own or inherited property value see `_.matchesProperty`.
     *
     * @static
     * @memberOf _
     * @category Collection
     * @param {Array|Object|string} collection The collection to search.
     * @param {Object} source The object of property values to match.
     * @returns {Array} Returns the new filtered array.
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36, 'active': false, 'pets': ['hoppy'] },
     *   { 'user': 'fred',   'age': 40, 'active': true, 'pets': ['baby puss', 'dino'] }
     * ];
     *
     * _.pluck(_.where(users, { 'age': 36, 'active': false }), 'user');
     * // => ['barney']
     *
     * _.pluck(_.where(users, { 'pets': ['dino'] }), 'user');
     * // => ['fred']
     */
    function where(collection, source) {
      return filter(collection, baseMatches(source));
    }

    /*------------------------------------------------------------------------*/

    /**
     * Gets the number of milliseconds that have elapsed since the Unix epoch
     * (1 January 1970 00:00:00 UTC).
     *
     * @static
     * @memberOf _
     * @category Date
     * @example
     *
     * _.defer(function(stamp) {
     *   console.log(_.now() - stamp);
     * }, _.now());
     * // => logs the number of milliseconds it took for the deferred function to be invoked
     */
    var now = nativeNow || function() {
      return new Date().getTime();
    };

    /*------------------------------------------------------------------------*/

    /**
     * The opposite of `_.before`; this method creates a function that invokes
     * `func` once it is called `n` or more times.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {number} n The number of calls before `func` is invoked.
     * @param {Function} func The function to restrict.
     * @returns {Function} Returns the new restricted function.
     * @example
     *
     * var saves = ['profile', 'settings'];
     *
     * var done = _.after(saves.length, function() {
     *   console.log('done saving!');
     * });
     *
     * _.forEach(saves, function(type) {
     *   asyncSave({ 'type': type, 'complete': done });
     * });
     * // => logs 'done saving!' after the two async saves have completed
     */
    function after(n, func) {
      if (typeof func != 'function') {
        if (typeof n == 'function') {
          var temp = n;
          n = func;
          func = temp;
        } else {
          throw new TypeError(FUNC_ERROR_TEXT);
        }
      }
      n = nativeIsFinite(n = +n) ? n : 0;
      return function() {
        if (--n < 1) {
          return func.apply(this, arguments);
        }
      };
    }

    /**
     * Creates a function that accepts up to `n` arguments ignoring any
     * additional arguments.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to cap arguments for.
     * @param {number} [n=func.length] The arity cap.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Function} Returns the new function.
     * @example
     *
     * _.map(['6', '8', '10'], _.ary(parseInt, 1));
     * // => [6, 8, 10]
     */
    function ary(func, n, guard) {
      if (guard && isIterateeCall(func, n, guard)) {
        n = undefined;
      }
      n = (func && n == null) ? func.length : nativeMax(+n || 0, 0);
      return createWrapper(func, ARY_FLAG, undefined, undefined, undefined, undefined, n);
    }

    /**
     * Creates a function that invokes `func`, with the `this` binding and arguments
     * of the created function, while it is called less than `n` times. Subsequent
     * calls to the created function return the result of the last `func` invocation.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {number} n The number of calls at which `func` is no longer invoked.
     * @param {Function} func The function to restrict.
     * @returns {Function} Returns the new restricted function.
     * @example
     *
     * jQuery('#add').on('click', _.before(5, addContactToList));
     * // => allows adding up to 4 contacts to the list
     */
    function before(n, func) {
      var result;
      if (typeof func != 'function') {
        if (typeof n == 'function') {
          var temp = n;
          n = func;
          func = temp;
        } else {
          throw new TypeError(FUNC_ERROR_TEXT);
        }
      }
      return function() {
        if (--n > 0) {
          result = func.apply(this, arguments);
        }
        if (n <= 1) {
          func = undefined;
        }
        return result;
      };
    }

    /**
     * Creates a function that invokes `func` with the `this` binding of `thisArg`
     * and prepends any additional `_.bind` arguments to those provided to the
     * bound function.
     *
     * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
     * may be used as a placeholder for partially applied arguments.
     *
     * **Note:** Unlike native `Function#bind` this method does not set the "length"
     * property of bound functions.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to bind.
     * @param {*} thisArg The `this` binding of `func`.
     * @param {...*} [partials] The arguments to be partially applied.
     * @returns {Function} Returns the new bound function.
     * @example
     *
     * var greet = function(greeting, punctuation) {
     *   return greeting + ' ' + this.user + punctuation;
     * };
     *
     * var object = { 'user': 'fred' };
     *
     * var bound = _.bind(greet, object, 'hi');
     * bound('!');
     * // => 'hi fred!'
     *
     * // using placeholders
     * var bound = _.bind(greet, object, _, '!');
     * bound('hi');
     * // => 'hi fred!'
     */
    var bind = restParam(function(func, thisArg, partials) {
      var bitmask = BIND_FLAG;
      if (partials.length) {
        var holders = replaceHolders(partials, bind.placeholder);
        bitmask |= PARTIAL_FLAG;
      }
      return createWrapper(func, bitmask, thisArg, partials, holders);
    });

    /**
     * Binds methods of an object to the object itself, overwriting the existing
     * method. Method names may be specified as individual arguments or as arrays
     * of method names. If no method names are provided all enumerable function
     * properties, own and inherited, of `object` are bound.
     *
     * **Note:** This method does not set the "length" property of bound functions.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Object} object The object to bind and assign the bound methods to.
     * @param {...(string|string[])} [methodNames] The object method names to bind,
     *  specified as individual method names or arrays of method names.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var view = {
     *   'label': 'docs',
     *   'onClick': function() {
     *     console.log('clicked ' + this.label);
     *   }
     * };
     *
     * _.bindAll(view);
     * jQuery('#docs').on('click', view.onClick);
     * // => logs 'clicked docs' when the element is clicked
     */
    var bindAll = restParam(function(object, methodNames) {
      methodNames = methodNames.length ? baseFlatten(methodNames) : functions(object);

      var index = -1,
          length = methodNames.length;

      while (++index < length) {
        var key = methodNames[index];
        object[key] = createWrapper(object[key], BIND_FLAG, object);
      }
      return object;
    });

    /**
     * Creates a function that invokes the method at `object[key]` and prepends
     * any additional `_.bindKey` arguments to those provided to the bound function.
     *
     * This method differs from `_.bind` by allowing bound functions to reference
     * methods that may be redefined or don't yet exist.
     * See [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
     * for more details.
     *
     * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
     * builds, may be used as a placeholder for partially applied arguments.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Object} object The object the method belongs to.
     * @param {string} key The key of the method.
     * @param {...*} [partials] The arguments to be partially applied.
     * @returns {Function} Returns the new bound function.
     * @example
     *
     * var object = {
     *   'user': 'fred',
     *   'greet': function(greeting, punctuation) {
     *     return greeting + ' ' + this.user + punctuation;
     *   }
     * };
     *
     * var bound = _.bindKey(object, 'greet', 'hi');
     * bound('!');
     * // => 'hi fred!'
     *
     * object.greet = function(greeting, punctuation) {
     *   return greeting + 'ya ' + this.user + punctuation;
     * };
     *
     * bound('!');
     * // => 'hiya fred!'
     *
     * // using placeholders
     * var bound = _.bindKey(object, 'greet', _, '!');
     * bound('hi');
     * // => 'hiya fred!'
     */
    var bindKey = restParam(function(object, key, partials) {
      var bitmask = BIND_FLAG | BIND_KEY_FLAG;
      if (partials.length) {
        var holders = replaceHolders(partials, bindKey.placeholder);
        bitmask |= PARTIAL_FLAG;
      }
      return createWrapper(key, bitmask, object, partials, holders);
    });

    /**
     * Creates a function that accepts one or more arguments of `func` that when
     * called either invokes `func` returning its result, if all `func` arguments
     * have been provided, or returns a function that accepts one or more of the
     * remaining `func` arguments, and so on. The arity of `func` may be specified
     * if `func.length` is not sufficient.
     *
     * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
     * may be used as a placeholder for provided arguments.
     *
     * **Note:** This method does not set the "length" property of curried functions.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to curry.
     * @param {number} [arity=func.length] The arity of `func`.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Function} Returns the new curried function.
     * @example
     *
     * var abc = function(a, b, c) {
     *   return [a, b, c];
     * };
     *
     * var curried = _.curry(abc);
     *
     * curried(1)(2)(3);
     * // => [1, 2, 3]
     *
     * curried(1, 2)(3);
     * // => [1, 2, 3]
     *
     * curried(1, 2, 3);
     * // => [1, 2, 3]
     *
     * // using placeholders
     * curried(1)(_, 3)(2);
     * // => [1, 2, 3]
     */
    var curry = createCurry(CURRY_FLAG);

    /**
     * This method is like `_.curry` except that arguments are applied to `func`
     * in the manner of `_.partialRight` instead of `_.partial`.
     *
     * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
     * builds, may be used as a placeholder for provided arguments.
     *
     * **Note:** This method does not set the "length" property of curried functions.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to curry.
     * @param {number} [arity=func.length] The arity of `func`.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Function} Returns the new curried function.
     * @example
     *
     * var abc = function(a, b, c) {
     *   return [a, b, c];
     * };
     *
     * var curried = _.curryRight(abc);
     *
     * curried(3)(2)(1);
     * // => [1, 2, 3]
     *
     * curried(2, 3)(1);
     * // => [1, 2, 3]
     *
     * curried(1, 2, 3);
     * // => [1, 2, 3]
     *
     * // using placeholders
     * curried(3)(1, _)(2);
     * // => [1, 2, 3]
     */
    var curryRight = createCurry(CURRY_RIGHT_FLAG);

    /**
     * Creates a debounced function that delays invoking `func` until after `wait`
     * milliseconds have elapsed since the last time the debounced function was
     * invoked. The debounced function comes with a `cancel` method to cancel
     * delayed invocations. Provide an options object to indicate that `func`
     * should be invoked on the leading and/or trailing edge of the `wait` timeout.
     * Subsequent calls to the debounced function return the result of the last
     * `func` invocation.
     *
     * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
     * on the trailing edge of the timeout only if the the debounced function is
     * invoked more than once during the `wait` timeout.
     *
     * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
     * for details over the differences between `_.debounce` and `_.throttle`.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to debounce.
     * @param {number} [wait=0] The number of milliseconds to delay.
     * @param {Object} [options] The options object.
     * @param {boolean} [options.leading=false] Specify invoking on the leading
     *  edge of the timeout.
     * @param {number} [options.maxWait] The maximum time `func` is allowed to be
     *  delayed before it is invoked.
     * @param {boolean} [options.trailing=true] Specify invoking on the trailing
     *  edge of the timeout.
     * @returns {Function} Returns the new debounced function.
     * @example
     *
     * // avoid costly calculations while the window size is in flux
     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
     *
     * // invoke `sendMail` when the click event is fired, debouncing subsequent calls
     * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
     *   'leading': true,
     *   'trailing': false
     * }));
     *
     * // ensure `batchLog` is invoked once after 1 second of debounced calls
     * var source = new EventSource('/stream');
     * jQuery(source).on('message', _.debounce(batchLog, 250, {
     *   'maxWait': 1000
     * }));
     *
     * // cancel a debounced call
     * var todoChanges = _.debounce(batchLog, 1000);
     * Object.observe(models.todo, todoChanges);
     *
     * Object.observe(models, function(changes) {
     *   if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) {
     *     todoChanges.cancel();
     *   }
     * }, ['delete']);
     *
     * // ...at some point `models.todo` is changed
     * models.todo.completed = true;
     *
     * // ...before 1 second has passed `models.todo` is deleted
     * // which cancels the debounced `todoChanges` call
     * delete models.todo;
     */
    function debounce(func, wait, options) {
      var args,
          maxTimeoutId,
          result,
          stamp,
          thisArg,
          timeoutId,
          trailingCall,
          lastCalled = 0,
          maxWait = false,
          trailing = true;

      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      wait = wait < 0 ? 0 : (+wait || 0);
      if (options === true) {
        var leading = true;
        trailing = false;
      } else if (isObject(options)) {
        leading = !!options.leading;
        maxWait = 'maxWait' in options && nativeMax(+options.maxWait || 0, wait);
        trailing = 'trailing' in options ? !!options.trailing : trailing;
      }

      function cancel() {
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        if (maxTimeoutId) {
          clearTimeout(maxTimeoutId);
        }
        lastCalled = 0;
        maxTimeoutId = timeoutId = trailingCall = undefined;
      }

      function complete(isCalled, id) {
        if (id) {
          clearTimeout(id);
        }
        maxTimeoutId = timeoutId = trailingCall = undefined;
        if (isCalled) {
          lastCalled = now();
          result = func.apply(thisArg, args);
          if (!timeoutId && !maxTimeoutId) {
            args = thisArg = undefined;
          }
        }
      }

      function delayed() {
        var remaining = wait - (now() - stamp);
        if (remaining <= 0 || remaining > wait) {
          complete(trailingCall, maxTimeoutId);
        } else {
          timeoutId = setTimeout(delayed, remaining);
        }
      }

      function maxDelayed() {
        complete(trailing, timeoutId);
      }

      function debounced() {
        args = arguments;
        stamp = now();
        thisArg = this;
        trailingCall = trailing && (timeoutId || !leading);

        if (maxWait === false) {
          var leadingCall = leading && !timeoutId;
        } else {
          if (!maxTimeoutId && !leading) {
            lastCalled = stamp;
          }
          var remaining = maxWait - (stamp - lastCalled),
              isCalled = remaining <= 0 || remaining > maxWait;

          if (isCalled) {
            if (maxTimeoutId) {
              maxTimeoutId = clearTimeout(maxTimeoutId);
            }
            lastCalled = stamp;
            result = func.apply(thisArg, args);
          }
          else if (!maxTimeoutId) {
            maxTimeoutId = setTimeout(maxDelayed, remaining);
          }
        }
        if (isCalled && timeoutId) {
          timeoutId = clearTimeout(timeoutId);
        }
        else if (!timeoutId && wait !== maxWait) {
          timeoutId = setTimeout(delayed, wait);
        }
        if (leadingCall) {
          isCalled = true;
          result = func.apply(thisArg, args);
        }
        if (isCalled && !timeoutId && !maxTimeoutId) {
          args = thisArg = undefined;
        }
        return result;
      }
      debounced.cancel = cancel;
      return debounced;
    }

    /**
     * Defers invoking the `func` until the current call stack has cleared. Any
     * additional arguments are provided to `func` when it is invoked.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to defer.
     * @param {...*} [args] The arguments to invoke the function with.
     * @returns {number} Returns the timer id.
     * @example
     *
     * _.defer(function(text) {
     *   console.log(text);
     * }, 'deferred');
     * // logs 'deferred' after one or more milliseconds
     */
    var defer = restParam(function(func, args) {
      return baseDelay(func, 1, args);
    });

    /**
     * Invokes `func` after `wait` milliseconds. Any additional arguments are
     * provided to `func` when it is invoked.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to delay.
     * @param {number} wait The number of milliseconds to delay invocation.
     * @param {...*} [args] The arguments to invoke the function with.
     * @returns {number} Returns the timer id.
     * @example
     *
     * _.delay(function(text) {
     *   console.log(text);
     * }, 1000, 'later');
     * // => logs 'later' after one second
     */
    var delay = restParam(function(func, wait, args) {
      return baseDelay(func, wait, args);
    });

    /**
     * Creates a function that returns the result of invoking the provided
     * functions with the `this` binding of the created function, where each
     * successive invocation is supplied the return value of the previous.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {...Function} [funcs] Functions to invoke.
     * @returns {Function} Returns the new function.
     * @example
     *
     * function square(n) {
     *   return n * n;
     * }
     *
     * var addSquare = _.flow(_.add, square);
     * addSquare(1, 2);
     * // => 9
     */
    var flow = createFlow();

    /**
     * This method is like `_.flow` except that it creates a function that
     * invokes the provided functions from right to left.
     *
     * @static
     * @memberOf _
     * @alias backflow, compose
     * @category Function
     * @param {...Function} [funcs] Functions to invoke.
     * @returns {Function} Returns the new function.
     * @example
     *
     * function square(n) {
     *   return n * n;
     * }
     *
     * var addSquare = _.flowRight(square, _.add);
     * addSquare(1, 2);
     * // => 9
     */
    var flowRight = createFlow(true);

    /**
     * Creates a function that memoizes the result of `func`. If `resolver` is
     * provided it determines the cache key for storing the result based on the
     * arguments provided to the memoized function. By default, the first argument
     * provided to the memoized function is coerced to a string and used as the
     * cache key. The `func` is invoked with the `this` binding of the memoized
     * function.
     *
     * **Note:** The cache is exposed as the `cache` property on the memoized
     * function. Its creation may be customized by replacing the `_.memoize.Cache`
     * constructor with one whose instances implement the [`Map`](http://ecma-international.org/ecma-262/6.0/#sec-properties-of-the-map-prototype-object)
     * method interface of `get`, `has`, and `set`.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to have its output memoized.
     * @param {Function} [resolver] The function to resolve the cache key.
     * @returns {Function} Returns the new memoizing function.
     * @example
     *
     * var upperCase = _.memoize(function(string) {
     *   return string.toUpperCase();
     * });
     *
     * upperCase('fred');
     * // => 'FRED'
     *
     * // modifying the result cache
     * upperCase.cache.set('fred', 'BARNEY');
     * upperCase('fred');
     * // => 'BARNEY'
     *
     * // replacing `_.memoize.Cache`
     * var object = { 'user': 'fred' };
     * var other = { 'user': 'barney' };
     * var identity = _.memoize(_.identity);
     *
     * identity(object);
     * // => { 'user': 'fred' }
     * identity(other);
     * // => { 'user': 'fred' }
     *
     * _.memoize.Cache = WeakMap;
     * var identity = _.memoize(_.identity);
     *
     * identity(object);
     * // => { 'user': 'fred' }
     * identity(other);
     * // => { 'user': 'barney' }
     */
    function memoize(func, resolver) {
      if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      var memoized = function() {
        var args = arguments,
            key = resolver ? resolver.apply(this, args) : args[0],
            cache = memoized.cache;

        if (cache.has(key)) {
          return cache.get(key);
        }
        var result = func.apply(this, args);
        memoized.cache = cache.set(key, result);
        return result;
      };
      memoized.cache = new memoize.Cache;
      return memoized;
    }

    /**
     * Creates a function that runs each argument through a corresponding
     * transform function.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to wrap.
     * @param {...(Function|Function[])} [transforms] The functions to transform
     * arguments, specified as individual functions or arrays of functions.
     * @returns {Function} Returns the new function.
     * @example
     *
     * function doubled(n) {
     *   return n * 2;
     * }
     *
     * function square(n) {
     *   return n * n;
     * }
     *
     * var modded = _.modArgs(function(x, y) {
     *   return [x, y];
     * }, square, doubled);
     *
     * modded(1, 2);
     * // => [1, 4]
     *
     * modded(5, 10);
     * // => [25, 20]
     */
    var modArgs = restParam(function(func, transforms) {
      transforms = baseFlatten(transforms);
      if (typeof func != 'function' || !arrayEvery(transforms, baseIsFunction)) {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      var length = transforms.length;
      return restParam(function(args) {
        var index = nativeMin(args.length, length);
        while (index--) {
          args[index] = transforms[index](args[index]);
        }
        return func.apply(this, args);
      });
    });

    /**
     * Creates a function that negates the result of the predicate `func`. The
     * `func` predicate is invoked with the `this` binding and arguments of the
     * created function.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} predicate The predicate to negate.
     * @returns {Function} Returns the new function.
     * @example
     *
     * function isEven(n) {
     *   return n % 2 == 0;
     * }
     *
     * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
     * // => [1, 3, 5]
     */
    function negate(predicate) {
      if (typeof predicate != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      return function() {
        return !predicate.apply(this, arguments);
      };
    }

    /**
     * Creates a function that is restricted to invoking `func` once. Repeat calls
     * to the function return the value of the first call. The `func` is invoked
     * with the `this` binding and arguments of the created function.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to restrict.
     * @returns {Function} Returns the new restricted function.
     * @example
     *
     * var initialize = _.once(createApplication);
     * initialize();
     * initialize();
     * // `initialize` invokes `createApplication` once
     */
    function once(func) {
      return before(2, func);
    }

    /**
     * Creates a function that invokes `func` with `partial` arguments prepended
     * to those provided to the new function. This method is like `_.bind` except
     * it does **not** alter the `this` binding.
     *
     * The `_.partial.placeholder` value, which defaults to `_` in monolithic
     * builds, may be used as a placeholder for partially applied arguments.
     *
     * **Note:** This method does not set the "length" property of partially
     * applied functions.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to partially apply arguments to.
     * @param {...*} [partials] The arguments to be partially applied.
     * @returns {Function} Returns the new partially applied function.
     * @example
     *
     * var greet = function(greeting, name) {
     *   return greeting + ' ' + name;
     * };
     *
     * var sayHelloTo = _.partial(greet, 'hello');
     * sayHelloTo('fred');
     * // => 'hello fred'
     *
     * // using placeholders
     * var greetFred = _.partial(greet, _, 'fred');
     * greetFred('hi');
     * // => 'hi fred'
     */
    var partial = createPartial(PARTIAL_FLAG);

    /**
     * This method is like `_.partial` except that partially applied arguments
     * are appended to those provided to the new function.
     *
     * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
     * builds, may be used as a placeholder for partially applied arguments.
     *
     * **Note:** This method does not set the "length" property of partially
     * applied functions.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to partially apply arguments to.
     * @param {...*} [partials] The arguments to be partially applied.
     * @returns {Function} Returns the new partially applied function.
     * @example
     *
     * var greet = function(greeting, name) {
     *   return greeting + ' ' + name;
     * };
     *
     * var greetFred = _.partialRight(greet, 'fred');
     * greetFred('hi');
     * // => 'hi fred'
     *
     * // using placeholders
     * var sayHelloTo = _.partialRight(greet, 'hello', _);
     * sayHelloTo('fred');
     * // => 'hello fred'
     */
    var partialRight = createPartial(PARTIAL_RIGHT_FLAG);

    /**
     * Creates a function that invokes `func` with arguments arranged according
     * to the specified indexes where the argument value at the first index is
     * provided as the first argument, the argument value at the second index is
     * provided as the second argument, and so on.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to rearrange arguments for.
     * @param {...(number|number[])} indexes The arranged argument indexes,
     *  specified as individual indexes or arrays of indexes.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var rearged = _.rearg(function(a, b, c) {
     *   return [a, b, c];
     * }, 2, 0, 1);
     *
     * rearged('b', 'c', 'a')
     * // => ['a', 'b', 'c']
     *
     * var map = _.rearg(_.map, [1, 0]);
     * map(function(n) {
     *   return n * 3;
     * }, [1, 2, 3]);
     * // => [3, 6, 9]
     */
    var rearg = restParam(function(func, indexes) {
      return createWrapper(func, REARG_FLAG, undefined, undefined, undefined, baseFlatten(indexes));
    });

    /**
     * Creates a function that invokes `func` with the `this` binding of the
     * created function and arguments from `start` and beyond provided as an array.
     *
     * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters).
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to apply a rest parameter to.
     * @param {number} [start=func.length-1] The start position of the rest parameter.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var say = _.restParam(function(what, names) {
     *   return what + ' ' + _.initial(names).join(', ') +
     *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
     * });
     *
     * say('hello', 'fred', 'barney', 'pebbles');
     * // => 'hello fred, barney, & pebbles'
     */
    function restParam(func, start) {
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0);
      return function() {
        var args = arguments,
            index = -1,
            length = nativeMax(args.length - start, 0),
            rest = Array(length);

        while (++index < length) {
          rest[index] = args[start + index];
        }
        switch (start) {
          case 0: return func.call(this, rest);
          case 1: return func.call(this, args[0], rest);
          case 2: return func.call(this, args[0], args[1], rest);
        }
        var otherArgs = Array(start + 1);
        index = -1;
        while (++index < start) {
          otherArgs[index] = args[index];
        }
        otherArgs[start] = rest;
        return func.apply(this, otherArgs);
      };
    }

    /**
     * Creates a function that invokes `func` with the `this` binding of the created
     * function and an array of arguments much like [`Function#apply`](https://es5.github.io/#x15.3.4.3).
     *
     * **Note:** This method is based on the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator).
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to spread arguments over.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var say = _.spread(function(who, what) {
     *   return who + ' says ' + what;
     * });
     *
     * say(['fred', 'hello']);
     * // => 'fred says hello'
     *
     * // with a Promise
     * var numbers = Promise.all([
     *   Promise.resolve(40),
     *   Promise.resolve(36)
     * ]);
     *
     * numbers.then(_.spread(function(x, y) {
     *   return x + y;
     * }));
     * // => a Promise of 76
     */
    function spread(func) {
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      return function(array) {
        return func.apply(this, array);
      };
    }

    /**
     * Creates a throttled function that only invokes `func` at most once per
     * every `wait` milliseconds. The throttled function comes with a `cancel`
     * method to cancel delayed invocations. Provide an options object to indicate
     * that `func` should be invoked on the leading and/or trailing edge of the
     * `wait` timeout. Subsequent calls to the throttled function return the
     * result of the last `func` call.
     *
     * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
     * on the trailing edge of the timeout only if the the throttled function is
     * invoked more than once during the `wait` timeout.
     *
     * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
     * for details over the differences between `_.throttle` and `_.debounce`.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {Function} func The function to throttle.
     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
     * @param {Object} [options] The options object.
     * @param {boolean} [options.leading=true] Specify invoking on the leading
     *  edge of the timeout.
     * @param {boolean} [options.trailing=true] Specify invoking on the trailing
     *  edge of the timeout.
     * @returns {Function} Returns the new throttled function.
     * @example
     *
     * // avoid excessively updating the position while scrolling
     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
     *
     * // invoke `renewToken` when the click event is fired, but not more than once every 5 minutes
     * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
     *   'trailing': false
     * }));
     *
     * // cancel a trailing throttled call
     * jQuery(window).on('popstate', throttled.cancel);
     */
    function throttle(func, wait, options) {
      var leading = true,
          trailing = true;

      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      if (options === false) {
        leading = false;
      } else if (isObject(options)) {
        leading = 'leading' in options ? !!options.leading : leading;
        trailing = 'trailing' in options ? !!options.trailing : trailing;
      }
      return debounce(func, wait, { 'leading': leading, 'maxWait': +wait, 'trailing': trailing });
    }

    /**
     * Creates a function that provides `value` to the wrapper function as its
     * first argument. Any additional arguments provided to the function are
     * appended to those provided to the wrapper function. The wrapper is invoked
     * with the `this` binding of the created function.
     *
     * @static
     * @memberOf _
     * @category Function
     * @param {*} value The value to wrap.
     * @param {Function} wrapper The wrapper function.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var p = _.wrap(_.escape, function(func, text) {
     *   return '<p>' + func(text) + '</p>';
     * });
     *
     * p('fred, barney, & pebbles');
     * // => '<p>fred, barney, &amp; pebbles</p>'
     */
    function wrap(value, wrapper) {
      wrapper = wrapper == null ? identity : wrapper;
      return createWrapper(wrapper, PARTIAL_FLAG, undefined, [value], []);
    }

    /*------------------------------------------------------------------------*/

    /**
     * Creates a clone of `value`. If `isDeep` is `true` nested objects are cloned,
     * otherwise they are assigned by reference. If `customizer` is provided it is
     * invoked to produce the cloned values. If `customizer` returns `undefined`
     * cloning is handled by the method instead. The `customizer` is bound to
     * `thisArg` and invoked with two argument; (value [, index|key, object]).
     *
     * **Note:** This method is loosely based on the
     * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
     * The enumerable properties of `arguments` objects and objects created by
     * constructors other than `Object` are cloned to plain `Object` objects. An
     * empty object is returned for uncloneable values such as functions, DOM nodes,
     * Maps, Sets, and WeakMaps.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to clone.
     * @param {boolean} [isDeep] Specify a deep clone.
     * @param {Function} [customizer] The function to customize cloning values.
     * @param {*} [thisArg] The `this` binding of `customizer`.
     * @returns {*} Returns the cloned value.
     * @example
     *
     * var users = [
     *   { 'user': 'barney' },
     *   { 'user': 'fred' }
     * ];
     *
     * var shallow = _.clone(users);
     * shallow[0] === users[0];
     * // => true
     *
     * var deep = _.clone(users, true);
     * deep[0] === users[0];
     * // => false
     *
     * // using a customizer callback
     * var el = _.clone(document.body, function(value) {
     *   if (_.isElement(value)) {
     *     return value.cloneNode(false);
     *   }
     * });
     *
     * el === document.body
     * // => false
     * el.nodeName
     * // => BODY
     * el.childNodes.length;
     * // => 0
     */
    function clone(value, isDeep, customizer, thisArg) {
      if (isDeep && typeof isDeep != 'boolean' && isIterateeCall(value, isDeep, customizer)) {
        isDeep = false;
      }
      else if (typeof isDeep == 'function') {
        thisArg = customizer;
        customizer = isDeep;
        isDeep = false;
      }
      return typeof customizer == 'function'
        ? baseClone(value, isDeep, bindCallback(customizer, thisArg, 1))
        : baseClone(value, isDeep);
    }

    /**
     * Creates a deep clone of `value`. If `customizer` is provided it is invoked
     * to produce the cloned values. If `customizer` returns `undefined` cloning
     * is handled by the method instead. The `customizer` is bound to `thisArg`
     * and invoked with two argument; (value [, index|key, object]).
     *
     * **Note:** This method is loosely based on the
     * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
     * The enumerable properties of `arguments` objects and objects created by
     * constructors other than `Object` are cloned to plain `Object` objects. An
     * empty object is returned for uncloneable values such as functions, DOM nodes,
     * Maps, Sets, and WeakMaps.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to deep clone.
     * @param {Function} [customizer] The function to customize cloning values.
     * @param {*} [thisArg] The `this` binding of `customizer`.
     * @returns {*} Returns the deep cloned value.
     * @example
     *
     * var users = [
     *   { 'user': 'barney' },
     *   { 'user': 'fred' }
     * ];
     *
     * var deep = _.cloneDeep(users);
     * deep[0] === users[0];
     * // => false
     *
     * // using a customizer callback
     * var el = _.cloneDeep(document.body, function(value) {
     *   if (_.isElement(value)) {
     *     return value.cloneNode(true);
     *   }
     * });
     *
     * el === document.body
     * // => false
     * el.nodeName
     * // => BODY
     * el.childNodes.length;
     * // => 20
     */
    function cloneDeep(value, customizer, thisArg) {
      return typeof customizer == 'function'
        ? baseClone(value, true, bindCallback(customizer, thisArg, 1))
        : baseClone(value, true);
    }

    /**
     * Checks if `value` is greater than `other`.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is greater than `other`, else `false`.
     * @example
     *
     * _.gt(3, 1);
     * // => true
     *
     * _.gt(3, 3);
     * // => false
     *
     * _.gt(1, 3);
     * // => false
     */
    function gt(value, other) {
      return value > other;
    }

    /**
     * Checks if `value` is greater than or equal to `other`.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is greater than or equal to `other`, else `false`.
     * @example
     *
     * _.gte(3, 1);
     * // => true
     *
     * _.gte(3, 3);
     * // => true
     *
     * _.gte(1, 3);
     * // => false
     */
    function gte(value, other) {
      return value >= other;
    }

    /**
     * Checks if `value` is classified as an `arguments` object.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
     * @example
     *
     * _.isArguments(function() { return arguments; }());
     * // => true
     *
     * _.isArguments([1, 2, 3]);
     * // => false
     */
    function isArguments(value) {
      return isObjectLike(value) && isArrayLike(value) &&
        hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
    }

    /**
     * Checks if `value` is classified as an `Array` object.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
     * @example
     *
     * _.isArray([1, 2, 3]);
     * // => true
     *
     * _.isArray(function() { return arguments; }());
     * // => false
     */
    var isArray = nativeIsArray || function(value) {
      return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;
    };

    /**
     * Checks if `value` is classified as a boolean primitive or object.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
     * @example
     *
     * _.isBoolean(false);
     * // => true
     *
     * _.isBoolean(null);
     * // => false
     */
    function isBoolean(value) {
      return value === true || value === false || (isObjectLike(value) && objToString.call(value) == boolTag);
    }

    /**
     * Checks if `value` is classified as a `Date` object.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
     * @example
     *
     * _.isDate(new Date);
     * // => true
     *
     * _.isDate('Mon April 23 2012');
     * // => false
     */
    function isDate(value) {
      return isObjectLike(value) && objToString.call(value) == dateTag;
    }

    /**
     * Checks if `value` is a DOM element.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
     * @example
     *
     * _.isElement(document.body);
     * // => true
     *
     * _.isElement('<body>');
     * // => false
     */
    function isElement(value) {
      return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value);
    }

    /**
     * Checks if `value` is empty. A value is considered empty unless it is an
     * `arguments` object, array, string, or jQuery-like collection with a length
     * greater than `0` or an object with own enumerable properties.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {Array|Object|string} value The value to inspect.
     * @returns {boolean} Returns `true` if `value` is empty, else `false`.
     * @example
     *
     * _.isEmpty(null);
     * // => true
     *
     * _.isEmpty(true);
     * // => true
     *
     * _.isEmpty(1);
     * // => true
     *
     * _.isEmpty([1, 2, 3]);
     * // => false
     *
     * _.isEmpty({ 'a': 1 });
     * // => false
     */
    function isEmpty(value) {
      if (value == null) {
        return true;
      }
      if (isArrayLike(value) && (isArray(value) || isString(value) || isArguments(value) ||
          (isObjectLike(value) && isFunction(value.splice)))) {
        return !value.length;
      }
      return !keys(value).length;
    }

    /**
     * Performs a deep comparison between two values to determine if they are
     * equivalent. If `customizer` is provided it is invoked to compare values.
     * If `customizer` returns `undefined` comparisons are handled by the method
     * instead. The `customizer` is bound to `thisArg` and invoked with three
     * arguments: (value, other [, index|key]).
     *
     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
     * numbers, `Object` objects, regexes, and strings. Objects are compared by
     * their own, not inherited, enumerable properties. Functions and DOM nodes
     * are **not** supported. Provide a customizer function to extend support
     * for comparing other values.
     *
     * @static
     * @memberOf _
     * @alias eq
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @param {Function} [customizer] The function to customize value comparisons.
     * @param {*} [thisArg] The `this` binding of `customizer`.
     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
     * @example
     *
     * var object = { 'user': 'fred' };
     * var other = { 'user': 'fred' };
     *
     * object == other;
     * // => false
     *
     * _.isEqual(object, other);
     * // => true
     *
     * // using a customizer callback
     * var array = ['hello', 'goodbye'];
     * var other = ['hi', 'goodbye'];
     *
     * _.isEqual(array, other, function(value, other) {
     *   if (_.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/)) {
     *     return true;
     *   }
     * });
     * // => true
     */
    function isEqual(value, other, customizer, thisArg) {
      customizer = typeof customizer == 'function' ? bindCallback(customizer, thisArg, 3) : undefined;
      var result = customizer ? customizer(value, other) : undefined;
      return  result === undefined ? baseIsEqual(value, other, customizer) : !!result;
    }

    /**
     * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
     * `SyntaxError`, `TypeError`, or `URIError` object.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an error object, else `false`.
     * @example
     *
     * _.isError(new Error);
     * // => true
     *
     * _.isError(Error);
     * // => false
     */
    function isError(value) {
      return isObjectLike(value) && typeof value.message == 'string' && objToString.call(value) == errorTag;
    }

    /**
     * Checks if `value` is a finite primitive number.
     *
     * **Note:** This method is based on [`Number.isFinite`](http://ecma-international.org/ecma-262/6.0/#sec-number.isfinite).
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a finite number, else `false`.
     * @example
     *
     * _.isFinite(10);
     * // => true
     *
     * _.isFinite('10');
     * // => false
     *
     * _.isFinite(true);
     * // => false
     *
     * _.isFinite(Object(10));
     * // => false
     *
     * _.isFinite(Infinity);
     * // => false
     */
    function isFinite(value) {
      return typeof value == 'number' && nativeIsFinite(value);
    }

    /**
     * Checks if `value` is classified as a `Function` object.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
     * @example
     *
     * _.isFunction(_);
     * // => true
     *
     * _.isFunction(/abc/);
     * // => false
     */
    function isFunction(value) {
      // The use of `Object#toString` avoids issues with the `typeof` operator
      // in older versions of Chrome and Safari which return 'function' for regexes
      // and Safari 8 equivalents which return 'object' for typed array constructors.
      return isObject(value) && objToString.call(value) == funcTag;
    }

    /**
     * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
     * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an object, else `false`.
     * @example
     *
     * _.isObject({});
     * // => true
     *
     * _.isObject([1, 2, 3]);
     * // => true
     *
     * _.isObject(1);
     * // => false
     */
    function isObject(value) {
      // Avoid a V8 JIT bug in Chrome 19-20.
      // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
      var type = typeof value;
      return !!value && (type == 'object' || type == 'function');
    }

    /**
     * Performs a deep comparison between `object` and `source` to determine if
     * `object` contains equivalent property values. If `customizer` is provided
     * it is invoked to compare values. If `customizer` returns `undefined`
     * comparisons are handled by the method instead. The `customizer` is bound
     * to `thisArg` and invoked with three arguments: (value, other, index|key).
     *
     * **Note:** This method supports comparing properties of arrays, booleans,
     * `Date` objects, numbers, `Object` objects, regexes, and strings. Functions
     * and DOM nodes are **not** supported. Provide a customizer function to extend
     * support for comparing other values.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {Object} object The object to inspect.
     * @param {Object} source The object of property values to match.
     * @param {Function} [customizer] The function to customize value comparisons.
     * @param {*} [thisArg] The `this` binding of `customizer`.
     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
     * @example
     *
     * var object = { 'user': 'fred', 'age': 40 };
     *
     * _.isMatch(object, { 'age': 40 });
     * // => true
     *
     * _.isMatch(object, { 'age': 36 });
     * // => false
     *
     * // using a customizer callback
     * var object = { 'greeting': 'hello' };
     * var source = { 'greeting': 'hi' };
     *
     * _.isMatch(object, source, function(value, other) {
     *   return _.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/) || undefined;
     * });
     * // => true
     */
    function isMatch(object, source, customizer, thisArg) {
      customizer = typeof customizer == 'function' ? bindCallback(customizer, thisArg, 3) : undefined;
      return baseIsMatch(object, getMatchData(source), customizer);
    }

    /**
     * Checks if `value` is `NaN`.
     *
     * **Note:** This method is not the same as [`isNaN`](https://es5.github.io/#x15.1.2.4)
     * which returns `true` for `undefined` and other non-numeric values.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
     * @example
     *
     * _.isNaN(NaN);
     * // => true
     *
     * _.isNaN(new Number(NaN));
     * // => true
     *
     * isNaN(undefined);
     * // => true
     *
     * _.isNaN(undefined);
     * // => false
     */
    function isNaN(value) {
      // An `NaN` primitive is the only value that is not equal to itself.
      // Perform the `toStringTag` check first to avoid errors with some host objects in IE.
      return isNumber(value) && value != +value;
    }

    /**
     * Checks if `value` is a native function.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a native function, else `false`.
     * @example
     *
     * _.isNative(Array.prototype.push);
     * // => true
     *
     * _.isNative(_);
     * // => false
     */
    function isNative(value) {
      if (value == null) {
        return false;
      }
      if (isFunction(value)) {
        return reIsNative.test(fnToString.call(value));
      }
      return isObjectLike(value) && reIsHostCtor.test(value);
    }

    /**
     * Checks if `value` is `null`.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
     * @example
     *
     * _.isNull(null);
     * // => true
     *
     * _.isNull(void 0);
     * // => false
     */
    function isNull(value) {
      return value === null;
    }

    /**
     * Checks if `value` is classified as a `Number` primitive or object.
     *
     * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are classified
     * as numbers, use the `_.isFinite` method.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
     * @example
     *
     * _.isNumber(8.4);
     * // => true
     *
     * _.isNumber(NaN);
     * // => true
     *
     * _.isNumber('8.4');
     * // => false
     */
    function isNumber(value) {
      return typeof value == 'number' || (isObjectLike(value) && objToString.call(value) == numberTag);
    }

    /**
     * Checks if `value` is a plain object, that is, an object created by the
     * `Object` constructor or one with a `[[Prototype]]` of `null`.
     *
     * **Note:** This method assumes objects created by the `Object` constructor
     * have no inherited enumerable properties.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     * }
     *
     * _.isPlainObject(new Foo);
     * // => false
     *
     * _.isPlainObject([1, 2, 3]);
     * // => false
     *
     * _.isPlainObject({ 'x': 0, 'y': 0 });
     * // => true
     *
     * _.isPlainObject(Object.create(null));
     * // => true
     */
    function isPlainObject(value) {
      var Ctor;

      // Exit early for non `Object` objects.
      if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isArguments(value)) ||
          (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
        return false;
      }
      // IE < 9 iterates inherited properties before own properties. If the first
      // iterated property is an object's own property then there are no inherited
      // enumerable properties.
      var result;
      // In most environments an object's own properties are iterated before
      // its inherited properties. If the last iterated property is an object's
      // own property then there are no inherited enumerable properties.
      baseForIn(value, function(subValue, key) {
        result = key;
      });
      return result === undefined || hasOwnProperty.call(value, result);
    }

    /**
     * Checks if `value` is classified as a `RegExp` object.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
     * @example
     *
     * _.isRegExp(/abc/);
     * // => true
     *
     * _.isRegExp('/abc/');
     * // => false
     */
    function isRegExp(value) {
      return isObject(value) && objToString.call(value) == regexpTag;
    }

    /**
     * Checks if `value` is classified as a `String` primitive or object.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
     * @example
     *
     * _.isString('abc');
     * // => true
     *
     * _.isString(1);
     * // => false
     */
    function isString(value) {
      return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag);
    }

    /**
     * Checks if `value` is classified as a typed array.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
     * @example
     *
     * _.isTypedArray(new Uint8Array);
     * // => true
     *
     * _.isTypedArray([]);
     * // => false
     */
    function isTypedArray(value) {
      return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)];
    }

    /**
     * Checks if `value` is `undefined`.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
     * @example
     *
     * _.isUndefined(void 0);
     * // => true
     *
     * _.isUndefined(null);
     * // => false
     */
    function isUndefined(value) {
      return value === undefined;
    }

    /**
     * Checks if `value` is less than `other`.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is less than `other`, else `false`.
     * @example
     *
     * _.lt(1, 3);
     * // => true
     *
     * _.lt(3, 3);
     * // => false
     *
     * _.lt(3, 1);
     * // => false
     */
    function lt(value, other) {
      return value < other;
    }

    /**
     * Checks if `value` is less than or equal to `other`.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is less than or equal to `other`, else `false`.
     * @example
     *
     * _.lte(1, 3);
     * // => true
     *
     * _.lte(3, 3);
     * // => true
     *
     * _.lte(3, 1);
     * // => false
     */
    function lte(value, other) {
      return value <= other;
    }

    /**
     * Converts `value` to an array.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to convert.
     * @returns {Array} Returns the converted array.
     * @example
     *
     * (function() {
     *   return _.toArray(arguments).slice(1);
     * }(1, 2, 3));
     * // => [2, 3]
     */
    function toArray(value) {
      var length = value ? getLength(value) : 0;
      if (!isLength(length)) {
        return values(value);
      }
      if (!length) {
        return [];
      }
      return arrayCopy(value);
    }

    /**
     * Converts `value` to a plain object flattening inherited enumerable
     * properties of `value` to own properties of the plain object.
     *
     * @static
     * @memberOf _
     * @category Lang
     * @param {*} value The value to convert.
     * @returns {Object} Returns the converted plain object.
     * @example
     *
     * function Foo() {
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.assign({ 'a': 1 }, new Foo);
     * // => { 'a': 1, 'b': 2 }
     *
     * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
     * // => { 'a': 1, 'b': 2, 'c': 3 }
     */
    function toPlainObject(value) {
      return baseCopy(value, keysIn(value));
    }

    /*------------------------------------------------------------------------*/

    /**
     * Recursively merges own enumerable properties of the source object(s), that
     * don't resolve to `undefined` into the destination object. Subsequent sources
     * overwrite property assignments of previous sources. If `customizer` is
     * provided it is invoked to produce the merged values of the destination and
     * source properties. If `customizer` returns `undefined` merging is handled
     * by the method instead. The `customizer` is bound to `thisArg` and invoked
     * with five arguments: (objectValue, sourceValue, key, object, source).
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} [sources] The source objects.
     * @param {Function} [customizer] The function to customize assigned values.
     * @param {*} [thisArg] The `this` binding of `customizer`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var users = {
     *   'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
     * };
     *
     * var ages = {
     *   'data': [{ 'age': 36 }, { 'age': 40 }]
     * };
     *
     * _.merge(users, ages);
     * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
     *
     * // using a customizer callback
     * var object = {
     *   'fruits': ['apple'],
     *   'vegetables': ['beet']
     * };
     *
     * var other = {
     *   'fruits': ['banana'],
     *   'vegetables': ['carrot']
     * };
     *
     * _.merge(object, other, function(a, b) {
     *   if (_.isArray(a)) {
     *     return a.concat(b);
     *   }
     * });
     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
     */
    var merge = createAssigner(baseMerge);

    /**
     * Assigns own enumerable properties of source object(s) to the destination
     * object. Subsequent sources overwrite property assignments of previous sources.
     * If `customizer` is provided it is invoked to produce the assigned values.
     * The `customizer` is bound to `thisArg` and invoked with five arguments:
     * (objectValue, sourceValue, key, object, source).
     *
     * **Note:** This method mutates `object` and is based on
     * [`Object.assign`](http://ecma-international.org/ecma-262/6.0/#sec-object.assign).
     *
     * @static
     * @memberOf _
     * @alias extend
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} [sources] The source objects.
     * @param {Function} [customizer] The function to customize assigned values.
     * @param {*} [thisArg] The `this` binding of `customizer`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * _.assign({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' });
     * // => { 'user': 'fred', 'age': 40 }
     *
     * // using a customizer callback
     * var defaults = _.partialRight(_.assign, function(value, other) {
     *   return _.isUndefined(value) ? other : value;
     * });
     *
     * defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
     * // => { 'user': 'barney', 'age': 36 }
     */
    var assign = createAssigner(function(object, source, customizer) {
      return customizer
        ? assignWith(object, source, customizer)
        : baseAssign(object, source);
    });

    /**
     * Creates an object that inherits from the given `prototype` object. If a
     * `properties` object is provided its own enumerable properties are assigned
     * to the created object.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} prototype The object to inherit from.
     * @param {Object} [properties] The properties to assign to the object.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Object} Returns the new object.
     * @example
     *
     * function Shape() {
     *   this.x = 0;
     *   this.y = 0;
     * }
     *
     * function Circle() {
     *   Shape.call(this);
     * }
     *
     * Circle.prototype = _.create(Shape.prototype, {
     *   'constructor': Circle
     * });
     *
     * var circle = new Circle;
     * circle instanceof Circle;
     * // => true
     *
     * circle instanceof Shape;
     * // => true
     */
    function create(prototype, properties, guard) {
      var result = baseCreate(prototype);
      if (guard && isIterateeCall(prototype, properties, guard)) {
        properties = undefined;
      }
      return properties ? baseAssign(result, properties) : result;
    }

    /**
     * Assigns own enumerable properties of source object(s) to the destination
     * object for all destination properties that resolve to `undefined`. Once a
     * property is set, additional values of the same property are ignored.
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} [sources] The source objects.
     * @returns {Object} Returns `object`.
     * @example
     *
     * _.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
     * // => { 'user': 'barney', 'age': 36 }
     */
    var defaults = createDefaults(assign, assignDefaults);

    /**
     * This method is like `_.defaults` except that it recursively assigns
     * default properties.
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} [sources] The source objects.
     * @returns {Object} Returns `object`.
     * @example
     *
     * _.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
     * // => { 'user': { 'name': 'barney', 'age': 36 } }
     *
     */
    var defaultsDeep = createDefaults(merge, mergeDefaults);

    /**
     * This method is like `_.find` except that it returns the key of the first
     * element `predicate` returns truthy for instead of the element itself.
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to search.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {string|undefined} Returns the key of the matched element, else `undefined`.
     * @example
     *
     * var users = {
     *   'barney':  { 'age': 36, 'active': true },
     *   'fred':    { 'age': 40, 'active': false },
     *   'pebbles': { 'age': 1,  'active': true }
     * };
     *
     * _.findKey(users, function(chr) {
     *   return chr.age < 40;
     * });
     * // => 'barney' (iteration order is not guaranteed)
     *
     * // using the `_.matches` callback shorthand
     * _.findKey(users, { 'age': 1, 'active': true });
     * // => 'pebbles'
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.findKey(users, 'active', false);
     * // => 'fred'
     *
     * // using the `_.property` callback shorthand
     * _.findKey(users, 'active');
     * // => 'barney'
     */
    var findKey = createFindKey(baseForOwn);

    /**
     * This method is like `_.findKey` except that it iterates over elements of
     * a collection in the opposite order.
     *
     * If a property name is provided for `predicate` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `predicate` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to search.
     * @param {Function|Object|string} [predicate=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {string|undefined} Returns the key of the matched element, else `undefined`.
     * @example
     *
     * var users = {
     *   'barney':  { 'age': 36, 'active': true },
     *   'fred':    { 'age': 40, 'active': false },
     *   'pebbles': { 'age': 1,  'active': true }
     * };
     *
     * _.findLastKey(users, function(chr) {
     *   return chr.age < 40;
     * });
     * // => returns `pebbles` assuming `_.findKey` returns `barney`
     *
     * // using the `_.matches` callback shorthand
     * _.findLastKey(users, { 'age': 36, 'active': true });
     * // => 'barney'
     *
     * // using the `_.matchesProperty` callback shorthand
     * _.findLastKey(users, 'active', false);
     * // => 'fred'
     *
     * // using the `_.property` callback shorthand
     * _.findLastKey(users, 'active');
     * // => 'pebbles'
     */
    var findLastKey = createFindKey(baseForOwnRight);

    /**
     * Iterates over own and inherited enumerable properties of an object invoking
     * `iteratee` for each property. The `iteratee` is bound to `thisArg` and invoked
     * with three arguments: (value, key, object). Iteratee functions may exit
     * iteration early by explicitly returning `false`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.forIn(new Foo, function(value, key) {
     *   console.log(key);
     * });
     * // => logs 'a', 'b', and 'c' (iteration order is not guaranteed)
     */
    var forIn = createForIn(baseFor);

    /**
     * This method is like `_.forIn` except that it iterates over properties of
     * `object` in the opposite order.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.forInRight(new Foo, function(value, key) {
     *   console.log(key);
     * });
     * // => logs 'c', 'b', and 'a' assuming `_.forIn ` logs 'a', 'b', and 'c'
     */
    var forInRight = createForIn(baseForRight);

    /**
     * Iterates over own enumerable properties of an object invoking `iteratee`
     * for each property. The `iteratee` is bound to `thisArg` and invoked with
     * three arguments: (value, key, object). Iteratee functions may exit iteration
     * early by explicitly returning `false`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.forOwn(new Foo, function(value, key) {
     *   console.log(key);
     * });
     * // => logs 'a' and 'b' (iteration order is not guaranteed)
     */
    var forOwn = createForOwn(baseForOwn);

    /**
     * This method is like `_.forOwn` except that it iterates over properties of
     * `object` in the opposite order.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.forOwnRight(new Foo, function(value, key) {
     *   console.log(key);
     * });
     * // => logs 'b' and 'a' assuming `_.forOwn` logs 'a' and 'b'
     */
    var forOwnRight = createForOwn(baseForOwnRight);

    /**
     * Creates an array of function property names from all enumerable properties,
     * own and inherited, of `object`.
     *
     * @static
     * @memberOf _
     * @alias methods
     * @category Object
     * @param {Object} object The object to inspect.
     * @returns {Array} Returns the new array of property names.
     * @example
     *
     * _.functions(_);
     * // => ['after', 'ary', 'assign', ...]
     */
    function functions(object) {
      return baseFunctions(object, keysIn(object));
    }

    /**
     * Gets the property value at `path` of `object`. If the resolved value is
     * `undefined` the `defaultValue` is used in its place.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @param {Array|string} path The path of the property to get.
     * @param {*} [defaultValue] The value returned if the resolved value is `undefined`.
     * @returns {*} Returns the resolved value.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
     *
     * _.get(object, 'a[0].b.c');
     * // => 3
     *
     * _.get(object, ['a', '0', 'b', 'c']);
     * // => 3
     *
     * _.get(object, 'a.b.c', 'default');
     * // => 'default'
     */
    function get(object, path, defaultValue) {
      var result = object == null ? undefined : baseGet(object, toPath(path), path + '');
      return result === undefined ? defaultValue : result;
    }

    /**
     * Checks if `path` is a direct property.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @param {Array|string} path The path to check.
     * @returns {boolean} Returns `true` if `path` is a direct property, else `false`.
     * @example
     *
     * var object = { 'a': { 'b': { 'c': 3 } } };
     *
     * _.has(object, 'a');
     * // => true
     *
     * _.has(object, 'a.b.c');
     * // => true
     *
     * _.has(object, ['a', 'b', 'c']);
     * // => true
     */
    function has(object, path) {
      if (object == null) {
        return false;
      }
      var result = hasOwnProperty.call(object, path);
      if (!result && !isKey(path)) {
        path = toPath(path);
        object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
        if (object == null) {
          return false;
        }
        path = last(path);
        result = hasOwnProperty.call(object, path);
      }
      return result || (isLength(object.length) && isIndex(path, object.length) &&
        (isArray(object) || isArguments(object)));
    }

    /**
     * Creates an object composed of the inverted keys and values of `object`.
     * If `object` contains duplicate values, subsequent values overwrite property
     * assignments of previous values unless `multiValue` is `true`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to invert.
     * @param {boolean} [multiValue] Allow multiple values per key.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Object} Returns the new inverted object.
     * @example
     *
     * var object = { 'a': 1, 'b': 2, 'c': 1 };
     *
     * _.invert(object);
     * // => { '1': 'c', '2': 'b' }
     *
     * // with `multiValue`
     * _.invert(object, true);
     * // => { '1': ['a', 'c'], '2': ['b'] }
     */
    function invert(object, multiValue, guard) {
      if (guard && isIterateeCall(object, multiValue, guard)) {
        multiValue = undefined;
      }
      var index = -1,
          props = keys(object),
          length = props.length,
          result = {};

      while (++index < length) {
        var key = props[index],
            value = object[key];

        if (multiValue) {
          if (hasOwnProperty.call(result, value)) {
            result[value].push(key);
          } else {
            result[value] = [key];
          }
        }
        else {
          result[value] = key;
        }
      }
      return result;
    }

    /**
     * Creates an array of the own enumerable property names of `object`.
     *
     * **Note:** Non-object values are coerced to objects. See the
     * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
     * for more details.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.keys(new Foo);
     * // => ['a', 'b'] (iteration order is not guaranteed)
     *
     * _.keys('hi');
     * // => ['0', '1']
     */
    var keys = !nativeKeys ? shimKeys : function(object) {
      var Ctor = object == null ? undefined : object.constructor;
      if ((typeof Ctor == 'function' && Ctor.prototype === object) ||
          (typeof object != 'function' && isArrayLike(object))) {
        return shimKeys(object);
      }
      return isObject(object) ? nativeKeys(object) : [];
    };

    /**
     * Creates an array of the own and inherited enumerable property names of `object`.
     *
     * **Note:** Non-object values are coerced to objects.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.keysIn(new Foo);
     * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
     */
    function keysIn(object) {
      if (object == null) {
        return [];
      }
      if (!isObject(object)) {
        object = Object(object);
      }
      var length = object.length;
      length = (length && isLength(length) &&
        (isArray(object) || isArguments(object)) && length) || 0;

      var Ctor = object.constructor,
          index = -1,
          isProto = typeof Ctor == 'function' && Ctor.prototype === object,
          result = Array(length),
          skipIndexes = length > 0;

      while (++index < length) {
        result[index] = (index + '');
      }
      for (var key in object) {
        if (!(skipIndexes && isIndex(key, length)) &&
            !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
          result.push(key);
        }
      }
      return result;
    }

    /**
     * The opposite of `_.mapValues`; this method creates an object with the
     * same values as `object` and keys generated by running each own enumerable
     * property of `object` through `iteratee`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Object} Returns the new mapped object.
     * @example
     *
     * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
     *   return key + value;
     * });
     * // => { 'a1': 1, 'b2': 2 }
     */
    var mapKeys = createObjectMapper(true);

    /**
     * Creates an object with the same keys as `object` and values generated by
     * running each own enumerable property of `object` through `iteratee`. The
     * iteratee function is bound to `thisArg` and invoked with three arguments:
     * (value, key, object).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
     *  per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Object} Returns the new mapped object.
     * @example
     *
     * _.mapValues({ 'a': 1, 'b': 2 }, function(n) {
     *   return n * 3;
     * });
     * // => { 'a': 3, 'b': 6 }
     *
     * var users = {
     *   'fred':    { 'user': 'fred',    'age': 40 },
     *   'pebbles': { 'user': 'pebbles', 'age': 1 }
     * };
     *
     * // using the `_.property` callback shorthand
     * _.mapValues(users, 'age');
     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
     */
    var mapValues = createObjectMapper();

    /**
     * The opposite of `_.pick`; this method creates an object composed of the
     * own and inherited enumerable properties of `object` that are not omitted.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The source object.
     * @param {Function|...(string|string[])} [predicate] The function invoked per
     *  iteration or property names to omit, specified as individual property
     *  names or arrays of property names.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Object} Returns the new object.
     * @example
     *
     * var object = { 'user': 'fred', 'age': 40 };
     *
     * _.omit(object, 'age');
     * // => { 'user': 'fred' }
     *
     * _.omit(object, _.isNumber);
     * // => { 'user': 'fred' }
     */
    var omit = restParam(function(object, props) {
      if (object == null) {
        return {};
      }
      if (typeof props[0] != 'function') {
        var props = arrayMap(baseFlatten(props), String);
        return pickByArray(object, baseDifference(keysIn(object), props));
      }
      var predicate = bindCallback(props[0], props[1], 3);
      return pickByCallback(object, function(value, key, object) {
        return !predicate(value, key, object);
      });
    });

    /**
     * Creates a two dimensional array of the key-value pairs for `object`,
     * e.g. `[[key1, value1], [key2, value2]]`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the new array of key-value pairs.
     * @example
     *
     * _.pairs({ 'barney': 36, 'fred': 40 });
     * // => [['barney', 36], ['fred', 40]] (iteration order is not guaranteed)
     */
    function pairs(object) {
      object = toObject(object);

      var index = -1,
          props = keys(object),
          length = props.length,
          result = Array(length);

      while (++index < length) {
        var key = props[index];
        result[index] = [key, object[key]];
      }
      return result;
    }

    /**
     * Creates an object composed of the picked `object` properties. Property
     * names may be specified as individual arguments or as arrays of property
     * names. If `predicate` is provided it is invoked for each property of `object`
     * picking the properties `predicate` returns truthy for. The predicate is
     * bound to `thisArg` and invoked with three arguments: (value, key, object).
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The source object.
     * @param {Function|...(string|string[])} [predicate] The function invoked per
     *  iteration or property names to pick, specified as individual property
     *  names or arrays of property names.
     * @param {*} [thisArg] The `this` binding of `predicate`.
     * @returns {Object} Returns the new object.
     * @example
     *
     * var object = { 'user': 'fred', 'age': 40 };
     *
     * _.pick(object, 'user');
     * // => { 'user': 'fred' }
     *
     * _.pick(object, _.isString);
     * // => { 'user': 'fred' }
     */
    var pick = restParam(function(object, props) {
      if (object == null) {
        return {};
      }
      return typeof props[0] == 'function'
        ? pickByCallback(object, bindCallback(props[0], props[1], 3))
        : pickByArray(object, baseFlatten(props));
    });

    /**
     * This method is like `_.get` except that if the resolved value is a function
     * it is invoked with the `this` binding of its parent object and its result
     * is returned.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @param {Array|string} path The path of the property to resolve.
     * @param {*} [defaultValue] The value returned if the resolved value is `undefined`.
     * @returns {*} Returns the resolved value.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
     *
     * _.result(object, 'a[0].b.c1');
     * // => 3
     *
     * _.result(object, 'a[0].b.c2');
     * // => 4
     *
     * _.result(object, 'a.b.c', 'default');
     * // => 'default'
     *
     * _.result(object, 'a.b.c', _.constant('default'));
     * // => 'default'
     */
    function result(object, path, defaultValue) {
      var result = object == null ? undefined : object[path];
      if (result === undefined) {
        if (object != null && !isKey(path, object)) {
          path = toPath(path);
          object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
          result = object == null ? undefined : object[last(path)];
        }
        result = result === undefined ? defaultValue : result;
      }
      return isFunction(result) ? result.call(object) : result;
    }

    /**
     * Sets the property value of `path` on `object`. If a portion of `path`
     * does not exist it is created.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to augment.
     * @param {Array|string} path The path of the property to set.
     * @param {*} value The value to set.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
     *
     * _.set(object, 'a[0].b.c', 4);
     * console.log(object.a[0].b.c);
     * // => 4
     *
     * _.set(object, 'x[0].y.z', 5);
     * console.log(object.x[0].y.z);
     * // => 5
     */
    function set(object, path, value) {
      if (object == null) {
        return object;
      }
      var pathKey = (path + '');
      path = (object[pathKey] != null || isKey(path, object)) ? [pathKey] : toPath(path);

      var index = -1,
          length = path.length,
          lastIndex = length - 1,
          nested = object;

      while (nested != null && ++index < length) {
        var key = path[index];
        if (isObject(nested)) {
          if (index == lastIndex) {
            nested[key] = value;
          } else if (nested[key] == null) {
            nested[key] = isIndex(path[index + 1]) ? [] : {};
          }
        }
        nested = nested[key];
      }
      return object;
    }

    /**
     * An alternative to `_.reduce`; this method transforms `object` to a new
     * `accumulator` object which is the result of running each of its own enumerable
     * properties through `iteratee`, with each invocation potentially mutating
     * the `accumulator` object. The `iteratee` is bound to `thisArg` and invoked
     * with four arguments: (accumulator, value, key, object). Iteratee functions
     * may exit iteration early by explicitly returning `false`.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Array|Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [accumulator] The custom accumulator value.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {*} Returns the accumulated value.
     * @example
     *
     * _.transform([2, 3, 4], function(result, n) {
     *   result.push(n *= n);
     *   return n % 2 == 0;
     * });
     * // => [4, 9]
     *
     * _.transform({ 'a': 1, 'b': 2 }, function(result, n, key) {
     *   result[key] = n * 3;
     * });
     * // => { 'a': 3, 'b': 6 }
     */
    function transform(object, iteratee, accumulator, thisArg) {
      var isArr = isArray(object) || isTypedArray(object);
      iteratee = getCallback(iteratee, thisArg, 4);

      if (accumulator == null) {
        if (isArr || isObject(object)) {
          var Ctor = object.constructor;
          if (isArr) {
            accumulator = isArray(object) ? new Ctor : [];
          } else {
            accumulator = baseCreate(isFunction(Ctor) ? Ctor.prototype : undefined);
          }
        } else {
          accumulator = {};
        }
      }
      (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) {
        return iteratee(accumulator, value, index, object);
      });
      return accumulator;
    }

    /**
     * Creates an array of the own enumerable property values of `object`.
     *
     * **Note:** Non-object values are coerced to objects.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property values.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.values(new Foo);
     * // => [1, 2] (iteration order is not guaranteed)
     *
     * _.values('hi');
     * // => ['h', 'i']
     */
    function values(object) {
      return baseValues(object, keys(object));
    }

    /**
     * Creates an array of the own and inherited enumerable property values
     * of `object`.
     *
     * **Note:** Non-object values are coerced to objects.
     *
     * @static
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property values.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.valuesIn(new Foo);
     * // => [1, 2, 3] (iteration order is not guaranteed)
     */
    function valuesIn(object) {
      return baseValues(object, keysIn(object));
    }

    /*------------------------------------------------------------------------*/

    /**
     * Checks if `n` is between `start` and up to but not including, `end`. If
     * `end` is not specified it is set to `start` with `start` then set to `0`.
     *
     * @static
     * @memberOf _
     * @category Number
     * @param {number} n The number to check.
     * @param {number} [start=0] The start of the range.
     * @param {number} end The end of the range.
     * @returns {boolean} Returns `true` if `n` is in the range, else `false`.
     * @example
     *
     * _.inRange(3, 2, 4);
     * // => true
     *
     * _.inRange(4, 8);
     * // => true
     *
     * _.inRange(4, 2);
     * // => false
     *
     * _.inRange(2, 2);
     * // => false
     *
     * _.inRange(1.2, 2);
     * // => true
     *
     * _.inRange(5.2, 4);
     * // => false
     */
    function inRange(value, start, end) {
      start = +start || 0;
      if (end === undefined) {
        end = start;
        start = 0;
      } else {
        end = +end || 0;
      }
      return value >= nativeMin(start, end) && value < nativeMax(start, end);
    }

    /**
     * Produces a random number between `min` and `max` (inclusive). If only one
     * argument is provided a number between `0` and the given number is returned.
     * If `floating` is `true`, or either `min` or `max` are floats, a floating-point
     * number is returned instead of an integer.
     *
     * @static
     * @memberOf _
     * @category Number
     * @param {number} [min=0] The minimum possible value.
     * @param {number} [max=1] The maximum possible value.
     * @param {boolean} [floating] Specify returning a floating-point number.
     * @returns {number} Returns the random number.
     * @example
     *
     * _.random(0, 5);
     * // => an integer between 0 and 5
     *
     * _.random(5);
     * // => also an integer between 0 and 5
     *
     * _.random(5, true);
     * // => a floating-point number between 0 and 5
     *
     * _.random(1.2, 5.2);
     * // => a floating-point number between 1.2 and 5.2
     */
    function random(min, max, floating) {
      if (floating && isIterateeCall(min, max, floating)) {
        max = floating = undefined;
      }
      var noMin = min == null,
          noMax = max == null;

      if (floating == null) {
        if (noMax && typeof min == 'boolean') {
          floating = min;
          min = 1;
        }
        else if (typeof max == 'boolean') {
          floating = max;
          noMax = true;
        }
      }
      if (noMin && noMax) {
        max = 1;
        noMax = false;
      }
      min = +min || 0;
      if (noMax) {
        max = min;
        min = 0;
      } else {
        max = +max || 0;
      }
      if (floating || min % 1 || max % 1) {
        var rand = nativeRandom();
        return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand + '').length - 1)))), max);
      }
      return baseRandom(min, max);
    }

    /*------------------------------------------------------------------------*/

    /**
     * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the camel cased string.
     * @example
     *
     * _.camelCase('Foo Bar');
     * // => 'fooBar'
     *
     * _.camelCase('--foo-bar');
     * // => 'fooBar'
     *
     * _.camelCase('__foo_bar__');
     * // => 'fooBar'
     */
    var camelCase = createCompounder(function(result, word, index) {
      word = word.toLowerCase();
      return result + (index ? (word.charAt(0).toUpperCase() + word.slice(1)) : word);
    });

    /**
     * Capitalizes the first character of `string`.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to capitalize.
     * @returns {string} Returns the capitalized string.
     * @example
     *
     * _.capitalize('fred');
     * // => 'Fred'
     */
    function capitalize(string) {
      string = baseToString(string);
      return string && (string.charAt(0).toUpperCase() + string.slice(1));
    }

    /**
     * Deburrs `string` by converting [latin-1 supplementary letters](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
     * to basic latin letters and removing [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to deburr.
     * @returns {string} Returns the deburred string.
     * @example
     *
     * _.deburr('déjà vu');
     * // => 'deja vu'
     */
    function deburr(string) {
      string = baseToString(string);
      return string && string.replace(reLatin1, deburrLetter).replace(reComboMark, '');
    }

    /**
     * Checks if `string` ends with the given target string.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to search.
     * @param {string} [target] The string to search for.
     * @param {number} [position=string.length] The position to search from.
     * @returns {boolean} Returns `true` if `string` ends with `target`, else `false`.
     * @example
     *
     * _.endsWith('abc', 'c');
     * // => true
     *
     * _.endsWith('abc', 'b');
     * // => false
     *
     * _.endsWith('abc', 'b', 2);
     * // => true
     */
    function endsWith(string, target, position) {
      string = baseToString(string);
      target = (target + '');

      var length = string.length;
      position = position === undefined
        ? length
        : nativeMin(position < 0 ? 0 : (+position || 0), length);

      position -= target.length;
      return position >= 0 && string.indexOf(target, position) == position;
    }

    /**
     * Converts the characters "&", "<", ">", '"', "'", and "\`", in `string` to
     * their corresponding HTML entities.
     *
     * **Note:** No other characters are escaped. To escape additional characters
     * use a third-party library like [_he_](https://mths.be/he).
     *
     * Though the ">" character is escaped for symmetry, characters like
     * ">" and "/" don't need escaping in HTML and have no special meaning
     * unless they're part of a tag or unquoted attribute value.
     * See [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
     * (under "semi-related fun fact") for more details.
     *
     * Backticks are escaped because in Internet Explorer < 9, they can break out
     * of attribute values or HTML comments. See [#59](https://html5sec.org/#59),
     * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and
     * [#133](https://html5sec.org/#133) of the [HTML5 Security Cheatsheet](https://html5sec.org/)
     * for more details.
     *
     * When working with HTML you should always [quote attribute values](http://wonko.com/post/html-escaping)
     * to reduce XSS vectors.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to escape.
     * @returns {string} Returns the escaped string.
     * @example
     *
     * _.escape('fred, barney, & pebbles');
     * // => 'fred, barney, &amp; pebbles'
     */
    function escape(string) {
      // Reset `lastIndex` because in IE < 9 `String#replace` does not.
      string = baseToString(string);
      return (string && reHasUnescapedHtml.test(string))
        ? string.replace(reUnescapedHtml, escapeHtmlChar)
        : string;
    }

    /**
     * Escapes the `RegExp` special characters "\", "/", "^", "$", ".", "|", "?",
     * "*", "+", "(", ")", "[", "]", "{" and "}" in `string`.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to escape.
     * @returns {string} Returns the escaped string.
     * @example
     *
     * _.escapeRegExp('[lodash](https://lodash.com/)');
     * // => '\[lodash\]\(https:\/\/lodash\.com\/\)'
     */
    function escapeRegExp(string) {
      string = baseToString(string);
      return (string && reHasRegExpChars.test(string))
        ? string.replace(reRegExpChars, escapeRegExpChar)
        : (string || '(?:)');
    }

    /**
     * Converts `string` to [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the kebab cased string.
     * @example
     *
     * _.kebabCase('Foo Bar');
     * // => 'foo-bar'
     *
     * _.kebabCase('fooBar');
     * // => 'foo-bar'
     *
     * _.kebabCase('__foo_bar__');
     * // => 'foo-bar'
     */
    var kebabCase = createCompounder(function(result, word, index) {
      return result + (index ? '-' : '') + word.toLowerCase();
    });

    /**
     * Pads `string` on the left and right sides if it's shorter than `length`.
     * Padding characters are truncated if they can't be evenly divided by `length`.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to pad.
     * @param {number} [length=0] The padding length.
     * @param {string} [chars=' '] The string used as padding.
     * @returns {string} Returns the padded string.
     * @example
     *
     * _.pad('abc', 8);
     * // => '  abc   '
     *
     * _.pad('abc', 8, '_-');
     * // => '_-abc_-_'
     *
     * _.pad('abc', 3);
     * // => 'abc'
     */
    function pad(string, length, chars) {
      string = baseToString(string);
      length = +length;

      var strLength = string.length;
      if (strLength >= length || !nativeIsFinite(length)) {
        return string;
      }
      var mid = (length - strLength) / 2,
          leftLength = nativeFloor(mid),
          rightLength = nativeCeil(mid);

      chars = createPadding('', rightLength, chars);
      return chars.slice(0, leftLength) + string + chars;
    }

    /**
     * Pads `string` on the left side if it's shorter than `length`. Padding
     * characters are truncated if they exceed `length`.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to pad.
     * @param {number} [length=0] The padding length.
     * @param {string} [chars=' '] The string used as padding.
     * @returns {string} Returns the padded string.
     * @example
     *
     * _.padLeft('abc', 6);
     * // => '   abc'
     *
     * _.padLeft('abc', 6, '_-');
     * // => '_-_abc'
     *
     * _.padLeft('abc', 3);
     * // => 'abc'
     */
    var padLeft = createPadDir();

    /**
     * Pads `string` on the right side if it's shorter than `length`. Padding
     * characters are truncated if they exceed `length`.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to pad.
     * @param {number} [length=0] The padding length.
     * @param {string} [chars=' '] The string used as padding.
     * @returns {string} Returns the padded string.
     * @example
     *
     * _.padRight('abc', 6);
     * // => 'abc   '
     *
     * _.padRight('abc', 6, '_-');
     * // => 'abc_-_'
     *
     * _.padRight('abc', 3);
     * // => 'abc'
     */
    var padRight = createPadDir(true);

    /**
     * Converts `string` to an integer of the specified radix. If `radix` is
     * `undefined` or `0`, a `radix` of `10` is used unless `value` is a hexadecimal,
     * in which case a `radix` of `16` is used.
     *
     * **Note:** This method aligns with the [ES5 implementation](https://es5.github.io/#E)
     * of `parseInt`.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} string The string to convert.
     * @param {number} [radix] The radix to interpret `value` by.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {number} Returns the converted integer.
     * @example
     *
     * _.parseInt('08');
     * // => 8
     *
     * _.map(['6', '08', '10'], _.parseInt);
     * // => [6, 8, 10]
     */
    function parseInt(string, radix, guard) {
      // Firefox < 21 and Opera < 15 follow ES3 for `parseInt`.
      // Chrome fails to trim leading <BOM> whitespace characters.
      // See https://code.google.com/p/v8/issues/detail?id=3109 for more details.
      if (guard ? isIterateeCall(string, radix, guard) : radix == null) {
        radix = 0;
      } else if (radix) {
        radix = +radix;
      }
      string = trim(string);
      return nativeParseInt(string, radix || (reHasHexPrefix.test(string) ? 16 : 10));
    }

    /**
     * Repeats the given string `n` times.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to repeat.
     * @param {number} [n=0] The number of times to repeat the string.
     * @returns {string} Returns the repeated string.
     * @example
     *
     * _.repeat('*', 3);
     * // => '***'
     *
     * _.repeat('abc', 2);
     * // => 'abcabc'
     *
     * _.repeat('abc', 0);
     * // => ''
     */
    function repeat(string, n) {
      var result = '';
      string = baseToString(string);
      n = +n;
      if (n < 1 || !string || !nativeIsFinite(n)) {
        return result;
      }
      // Leverage the exponentiation by squaring algorithm for a faster repeat.
      // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
      do {
        if (n % 2) {
          result += string;
        }
        n = nativeFloor(n / 2);
        string += string;
      } while (n);

      return result;
    }

    /**
     * Converts `string` to [snake case](https://en.wikipedia.org/wiki/Snake_case).
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the snake cased string.
     * @example
     *
     * _.snakeCase('Foo Bar');
     * // => 'foo_bar'
     *
     * _.snakeCase('fooBar');
     * // => 'foo_bar'
     *
     * _.snakeCase('--foo-bar');
     * // => 'foo_bar'
     */
    var snakeCase = createCompounder(function(result, word, index) {
      return result + (index ? '_' : '') + word.toLowerCase();
    });

    /**
     * Converts `string` to [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the start cased string.
     * @example
     *
     * _.startCase('--foo-bar');
     * // => 'Foo Bar'
     *
     * _.startCase('fooBar');
     * // => 'Foo Bar'
     *
     * _.startCase('__foo_bar__');
     * // => 'Foo Bar'
     */
    var startCase = createCompounder(function(result, word, index) {
      return result + (index ? ' ' : '') + (word.charAt(0).toUpperCase() + word.slice(1));
    });

    /**
     * Checks if `string` starts with the given target string.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to search.
     * @param {string} [target] The string to search for.
     * @param {number} [position=0] The position to search from.
     * @returns {boolean} Returns `true` if `string` starts with `target`, else `false`.
     * @example
     *
     * _.startsWith('abc', 'a');
     * // => true
     *
     * _.startsWith('abc', 'b');
     * // => false
     *
     * _.startsWith('abc', 'b', 1);
     * // => true
     */
    function startsWith(string, target, position) {
      string = baseToString(string);
      position = position == null
        ? 0
        : nativeMin(position < 0 ? 0 : (+position || 0), string.length);

      return string.lastIndexOf(target, position) == position;
    }

    /**
     * Creates a compiled template function that can interpolate data properties
     * in "interpolate" delimiters, HTML-escape interpolated data properties in
     * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
     * properties may be accessed as free variables in the template. If a setting
     * object is provided it takes precedence over `_.templateSettings` values.
     *
     * **Note:** In the development build `_.template` utilizes
     * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
     * for easier debugging.
     *
     * For more information on precompiling templates see
     * [lodash's custom builds documentation](https://lodash.com/custom-builds).
     *
     * For more information on Chrome extension sandboxes see
     * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The template string.
     * @param {Object} [options] The options object.
     * @param {RegExp} [options.escape] The HTML "escape" delimiter.
     * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
     * @param {Object} [options.imports] An object to import into the template as free variables.
     * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
     * @param {string} [options.sourceURL] The sourceURL of the template's compiled source.
     * @param {string} [options.variable] The data object variable name.
     * @param- {Object} [otherOptions] Enables the legacy `options` param signature.
     * @returns {Function} Returns the compiled template function.
     * @example
     *
     * // using the "interpolate" delimiter to create a compiled template
     * var compiled = _.template('hello <%= user %>!');
     * compiled({ 'user': 'fred' });
     * // => 'hello fred!'
     *
     * // using the HTML "escape" delimiter to escape data property values
     * var compiled = _.template('<b><%- value %></b>');
     * compiled({ 'value': '<script>' });
     * // => '<b>&lt;script&gt;</b>'
     *
     * // using the "evaluate" delimiter to execute JavaScript and generate HTML
     * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
     * compiled({ 'users': ['fred', 'barney'] });
     * // => '<li>fred</li><li>barney</li>'
     *
     * // using the internal `print` function in "evaluate" delimiters
     * var compiled = _.template('<% print("hello " + user); %>!');
     * compiled({ 'user': 'barney' });
     * // => 'hello barney!'
     *
     * // using the ES delimiter as an alternative to the default "interpolate" delimiter
     * var compiled = _.template('hello ${ user }!');
     * compiled({ 'user': 'pebbles' });
     * // => 'hello pebbles!'
     *
     * // using custom template delimiters
     * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
     * var compiled = _.template('hello {{ user }}!');
     * compiled({ 'user': 'mustache' });
     * // => 'hello mustache!'
     *
     * // using backslashes to treat delimiters as plain text
     * var compiled = _.template('<%= "\\<%- value %\\>" %>');
     * compiled({ 'value': 'ignored' });
     * // => '<%- value %>'
     *
     * // using the `imports` option to import `jQuery` as `jq`
     * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
     * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
     * compiled({ 'users': ['fred', 'barney'] });
     * // => '<li>fred</li><li>barney</li>'
     *
     * // using the `sourceURL` option to specify a custom sourceURL for the template
     * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
     * compiled(data);
     * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
     *
     * // using the `variable` option to ensure a with-statement isn't used in the compiled template
     * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
     * compiled.source;
     * // => function(data) {
     * //   var __t, __p = '';
     * //   __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
     * //   return __p;
     * // }
     *
     * // using the `source` property to inline compiled templates for meaningful
     * // line numbers in error messages and a stack trace
     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
     *   var JST = {\
     *     "main": ' + _.template(mainText).source + '\
     *   };\
     * ');
     */
    function template(string, options, otherOptions) {
      // Based on John Resig's `tmpl` implementation (http://ejohn.org/blog/javascript-micro-templating/)
      // and Laura Doktorova's doT.js (https://github.com/olado/doT).
      var settings = lodash.templateSettings;

      if (otherOptions && isIterateeCall(string, options, otherOptions)) {
        options = otherOptions = undefined;
      }
      string = baseToString(string);
      options = assignWith(baseAssign({}, otherOptions || options), settings, assignOwnDefaults);

      var imports = assignWith(baseAssign({}, options.imports), settings.imports, assignOwnDefaults),
          importsKeys = keys(imports),
          importsValues = baseValues(imports, importsKeys);

      var isEscaping,
          isEvaluating,
          index = 0,
          interpolate = options.interpolate || reNoMatch,
          source = "__p += '";

      // Compile the regexp to match each delimiter.
      var reDelimiters = RegExp(
        (options.escape || reNoMatch).source + '|' +
        interpolate.source + '|' +
        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
        (options.evaluate || reNoMatch).source + '|$'
      , 'g');

      // Use a sourceURL for easier debugging.
      var sourceURL = '//# sourceURL=' +
        ('sourceURL' in options
          ? options.sourceURL
          : ('lodash.templateSources[' + (++templateCounter) + ']')
        ) + '\n';

      string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
        interpolateValue || (interpolateValue = esTemplateValue);

        // Escape characters that can't be included in string literals.
        source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);

        // Replace delimiters with snippets.
        if (escapeValue) {
          isEscaping = true;
          source += "' +\n__e(" + escapeValue + ") +\n'";
        }
        if (evaluateValue) {
          isEvaluating = true;
          source += "';\n" + evaluateValue + ";\n__p += '";
        }
        if (interpolateValue) {
          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
        }
        index = offset + match.length;

        // The JS engine embedded in Adobe products requires returning the `match`
        // string in order to produce the correct `offset` value.
        return match;
      });

      source += "';\n";

      // If `variable` is not specified wrap a with-statement around the generated
      // code to add the data object to the top of the scope chain.
      var variable = options.variable;
      if (!variable) {
        source = 'with (obj) {\n' + source + '\n}\n';
      }
      // Cleanup code by stripping empty strings.
      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
        .replace(reEmptyStringMiddle, '$1')
        .replace(reEmptyStringTrailing, '$1;');

      // Frame code as the function body.
      source = 'function(' + (variable || 'obj') + ') {\n' +
        (variable
          ? ''
          : 'obj || (obj = {});\n'
        ) +
        "var __t, __p = ''" +
        (isEscaping
           ? ', __e = _.escape'
           : ''
        ) +
        (isEvaluating
          ? ', __j = Array.prototype.join;\n' +
            "function print() { __p += __j.call(arguments, '') }\n"
          : ';\n'
        ) +
        source +
        'return __p\n}';

      var result = attempt(function() {
        return Function(importsKeys, sourceURL + 'return ' + source).apply(undefined, importsValues);
      });

      // Provide the compiled function's source by its `toString` method or
      // the `source` property as a convenience for inlining compiled templates.
      result.source = source;
      if (isError(result)) {
        throw result;
      }
      return result;
    }

    /**
     * Removes leading and trailing whitespace or specified characters from `string`.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to trim.
     * @param {string} [chars=whitespace] The characters to trim.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {string} Returns the trimmed string.
     * @example
     *
     * _.trim('  abc  ');
     * // => 'abc'
     *
     * _.trim('-_-abc-_-', '_-');
     * // => 'abc'
     *
     * _.map(['  foo  ', '  bar  '], _.trim);
     * // => ['foo', 'bar']
     */
    function trim(string, chars, guard) {
      var value = string;
      string = baseToString(string);
      if (!string) {
        return string;
      }
      if (guard ? isIterateeCall(value, chars, guard) : chars == null) {
        return string.slice(trimmedLeftIndex(string), trimmedRightIndex(string) + 1);
      }
      chars = (chars + '');
      return string.slice(charsLeftIndex(string, chars), charsRightIndex(string, chars) + 1);
    }

    /**
     * Removes leading whitespace or specified characters from `string`.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to trim.
     * @param {string} [chars=whitespace] The characters to trim.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {string} Returns the trimmed string.
     * @example
     *
     * _.trimLeft('  abc  ');
     * // => 'abc  '
     *
     * _.trimLeft('-_-abc-_-', '_-');
     * // => 'abc-_-'
     */
    function trimLeft(string, chars, guard) {
      var value = string;
      string = baseToString(string);
      if (!string) {
        return string;
      }
      if (guard ? isIterateeCall(value, chars, guard) : chars == null) {
        return string.slice(trimmedLeftIndex(string));
      }
      return string.slice(charsLeftIndex(string, (chars + '')));
    }

    /**
     * Removes trailing whitespace or specified characters from `string`.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to trim.
     * @param {string} [chars=whitespace] The characters to trim.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {string} Returns the trimmed string.
     * @example
     *
     * _.trimRight('  abc  ');
     * // => '  abc'
     *
     * _.trimRight('-_-abc-_-', '_-');
     * // => '-_-abc'
     */
    function trimRight(string, chars, guard) {
      var value = string;
      string = baseToString(string);
      if (!string) {
        return string;
      }
      if (guard ? isIterateeCall(value, chars, guard) : chars == null) {
        return string.slice(0, trimmedRightIndex(string) + 1);
      }
      return string.slice(0, charsRightIndex(string, (chars + '')) + 1);
    }

    /**
     * Truncates `string` if it's longer than the given maximum string length.
     * The last characters of the truncated string are replaced with the omission
     * string which defaults to "...".
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to truncate.
     * @param {Object|number} [options] The options object or maximum string length.
     * @param {number} [options.length=30] The maximum string length.
     * @param {string} [options.omission='...'] The string to indicate text is omitted.
     * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {string} Returns the truncated string.
     * @example
     *
     * _.trunc('hi-diddly-ho there, neighborino');
     * // => 'hi-diddly-ho there, neighbo...'
     *
     * _.trunc('hi-diddly-ho there, neighborino', 24);
     * // => 'hi-diddly-ho there, n...'
     *
     * _.trunc('hi-diddly-ho there, neighborino', {
     *   'length': 24,
     *   'separator': ' '
     * });
     * // => 'hi-diddly-ho there,...'
     *
     * _.trunc('hi-diddly-ho there, neighborino', {
     *   'length': 24,
     *   'separator': /,? +/
     * });
     * // => 'hi-diddly-ho there...'
     *
     * _.trunc('hi-diddly-ho there, neighborino', {
     *   'omission': ' [...]'
     * });
     * // => 'hi-diddly-ho there, neig [...]'
     */
    function trunc(string, options, guard) {
      if (guard && isIterateeCall(string, options, guard)) {
        options = undefined;
      }
      var length = DEFAULT_TRUNC_LENGTH,
          omission = DEFAULT_TRUNC_OMISSION;

      if (options != null) {
        if (isObject(options)) {
          var separator = 'separator' in options ? options.separator : separator;
          length = 'length' in options ? (+options.length || 0) : length;
          omission = 'omission' in options ? baseToString(options.omission) : omission;
        } else {
          length = +options || 0;
        }
      }
      string = baseToString(string);
      if (length >= string.length) {
        return string;
      }
      var end = length - omission.length;
      if (end < 1) {
        return omission;
      }
      var result = string.slice(0, end);
      if (separator == null) {
        return result + omission;
      }
      if (isRegExp(separator)) {
        if (string.slice(end).search(separator)) {
          var match,
              newEnd,
              substring = string.slice(0, end);

          if (!separator.global) {
            separator = RegExp(separator.source, (reFlags.exec(separator) || '') + 'g');
          }
          separator.lastIndex = 0;
          while ((match = separator.exec(substring))) {
            newEnd = match.index;
          }
          result = result.slice(0, newEnd == null ? end : newEnd);
        }
      } else if (string.indexOf(separator, end) != end) {
        var index = result.lastIndexOf(separator);
        if (index > -1) {
          result = result.slice(0, index);
        }
      }
      return result + omission;
    }

    /**
     * The inverse of `_.escape`; this method converts the HTML entities
     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, `&#39;`, and `&#96;` in `string` to their
     * corresponding characters.
     *
     * **Note:** No other HTML entities are unescaped. To unescape additional HTML
     * entities use a third-party library like [_he_](https://mths.be/he).
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to unescape.
     * @returns {string} Returns the unescaped string.
     * @example
     *
     * _.unescape('fred, barney, &amp; pebbles');
     * // => 'fred, barney, & pebbles'
     */
    function unescape(string) {
      string = baseToString(string);
      return (string && reHasEscapedHtml.test(string))
        ? string.replace(reEscapedHtml, unescapeHtmlChar)
        : string;
    }

    /**
     * Splits `string` into an array of its words.
     *
     * @static
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to inspect.
     * @param {RegExp|string} [pattern] The pattern to match words.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Array} Returns the words of `string`.
     * @example
     *
     * _.words('fred, barney, & pebbles');
     * // => ['fred', 'barney', 'pebbles']
     *
     * _.words('fred, barney, & pebbles', /[^, ]+/g);
     * // => ['fred', 'barney', '&', 'pebbles']
     */
    function words(string, pattern, guard) {
      if (guard && isIterateeCall(string, pattern, guard)) {
        pattern = undefined;
      }
      string = baseToString(string);
      return string.match(pattern || reWords) || [];
    }

    /*------------------------------------------------------------------------*/

    /**
     * Attempts to invoke `func`, returning either the result or the caught error
     * object. Any additional arguments are provided to `func` when it is invoked.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {Function} func The function to attempt.
     * @returns {*} Returns the `func` result or error object.
     * @example
     *
     * // avoid throwing errors for invalid selectors
     * var elements = _.attempt(function(selector) {
     *   return document.querySelectorAll(selector);
     * }, '>_>');
     *
     * if (_.isError(elements)) {
     *   elements = [];
     * }
     */
    var attempt = restParam(function(func, args) {
      try {
        return func.apply(undefined, args);
      } catch(e) {
        return isError(e) ? e : new Error(e);
      }
    });

    /**
     * Creates a function that invokes `func` with the `this` binding of `thisArg`
     * and arguments of the created function. If `func` is a property name the
     * created callback returns the property value for a given element. If `func`
     * is an object the created callback returns `true` for elements that contain
     * the equivalent object properties, otherwise it returns `false`.
     *
     * @static
     * @memberOf _
     * @alias iteratee
     * @category Utility
     * @param {*} [func=_.identity] The value to convert to a callback.
     * @param {*} [thisArg] The `this` binding of `func`.
     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
     * @returns {Function} Returns the callback.
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36 },
     *   { 'user': 'fred',   'age': 40 }
     * ];
     *
     * // wrap to create custom callback shorthands
     * _.callback = _.wrap(_.callback, function(callback, func, thisArg) {
     *   var match = /^(.+?)__([gl]t)(.+)$/.exec(func);
     *   if (!match) {
     *     return callback(func, thisArg);
     *   }
     *   return function(object) {
     *     return match[2] == 'gt'
     *       ? object[match[1]] > match[3]
     *       : object[match[1]] < match[3];
     *   };
     * });
     *
     * _.filter(users, 'age__gt36');
     * // => [{ 'user': 'fred', 'age': 40 }]
     */
    function callback(func, thisArg, guard) {
      if (guard && isIterateeCall(func, thisArg, guard)) {
        thisArg = undefined;
      }
      return isObjectLike(func)
        ? matches(func)
        : baseCallback(func, thisArg);
    }

    /**
     * Creates a function that returns `value`.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {*} value The value to return from the new function.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var object = { 'user': 'fred' };
     * var getter = _.constant(object);
     *
     * getter() === object;
     * // => true
     */
    function constant(value) {
      return function() {
        return value;
      };
    }

    /**
     * This method returns the first argument provided to it.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {*} value Any value.
     * @returns {*} Returns `value`.
     * @example
     *
     * var object = { 'user': 'fred' };
     *
     * _.identity(object) === object;
     * // => true
     */
    function identity(value) {
      return value;
    }

    /**
     * Creates a function that performs a deep comparison between a given object
     * and `source`, returning `true` if the given object has equivalent property
     * values, else `false`.
     *
     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
     * numbers, `Object` objects, regexes, and strings. Objects are compared by
     * their own, not inherited, enumerable properties. For comparing a single
     * own or inherited property value see `_.matchesProperty`.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {Object} source The object of property values to match.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36, 'active': true },
     *   { 'user': 'fred',   'age': 40, 'active': false }
     * ];
     *
     * _.filter(users, _.matches({ 'age': 40, 'active': false }));
     * // => [{ 'user': 'fred', 'age': 40, 'active': false }]
     */
    function matches(source) {
      return baseMatches(baseClone(source, true));
    }

    /**
     * Creates a function that compares the property value of `path` on a given
     * object to `value`.
     *
     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
     * numbers, `Object` objects, regexes, and strings. Objects are compared by
     * their own, not inherited, enumerable properties.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {Array|string} path The path of the property to get.
     * @param {*} srcValue The value to match.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var users = [
     *   { 'user': 'barney' },
     *   { 'user': 'fred' }
     * ];
     *
     * _.find(users, _.matchesProperty('user', 'fred'));
     * // => { 'user': 'fred' }
     */
    function matchesProperty(path, srcValue) {
      return baseMatchesProperty(path, baseClone(srcValue, true));
    }

    /**
     * Creates a function that invokes the method at `path` on a given object.
     * Any additional arguments are provided to the invoked method.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {Array|string} path The path of the method to invoke.
     * @param {...*} [args] The arguments to invoke the method with.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var objects = [
     *   { 'a': { 'b': { 'c': _.constant(2) } } },
     *   { 'a': { 'b': { 'c': _.constant(1) } } }
     * ];
     *
     * _.map(objects, _.method('a.b.c'));
     * // => [2, 1]
     *
     * _.invoke(_.sortBy(objects, _.method(['a', 'b', 'c'])), 'a.b.c');
     * // => [1, 2]
     */
    var method = restParam(function(path, args) {
      return function(object) {
        return invokePath(object, path, args);
      };
    });

    /**
     * The opposite of `_.method`; this method creates a function that invokes
     * the method at a given path on `object`. Any additional arguments are
     * provided to the invoked method.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {Object} object The object to query.
     * @param {...*} [args] The arguments to invoke the method with.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var array = _.times(3, _.constant),
     *     object = { 'a': array, 'b': array, 'c': array };
     *
     * _.map(['a[2]', 'c[0]'], _.methodOf(object));
     * // => [2, 0]
     *
     * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
     * // => [2, 0]
     */
    var methodOf = restParam(function(object, args) {
      return function(path) {
        return invokePath(object, path, args);
      };
    });

    /**
     * Adds all own enumerable function properties of a source object to the
     * destination object. If `object` is a function then methods are added to
     * its prototype as well.
     *
     * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
     * avoid conflicts caused by modifying the original.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {Function|Object} [object=lodash] The destination object.
     * @param {Object} source The object of functions to add.
     * @param {Object} [options] The options object.
     * @param {boolean} [options.chain=true] Specify whether the functions added
     *  are chainable.
     * @returns {Function|Object} Returns `object`.
     * @example
     *
     * function vowels(string) {
     *   return _.filter(string, function(v) {
     *     return /[aeiou]/i.test(v);
     *   });
     * }
     *
     * _.mixin({ 'vowels': vowels });
     * _.vowels('fred');
     * // => ['e']
     *
     * _('fred').vowels().value();
     * // => ['e']
     *
     * _.mixin({ 'vowels': vowels }, { 'chain': false });
     * _('fred').vowels();
     * // => ['e']
     */
    function mixin(object, source, options) {
      if (options == null) {
        var isObj = isObject(source),
            props = isObj ? keys(source) : undefined,
            methodNames = (props && props.length) ? baseFunctions(source, props) : undefined;

        if (!(methodNames ? methodNames.length : isObj)) {
          methodNames = false;
          options = source;
          source = object;
          object = this;
        }
      }
      if (!methodNames) {
        methodNames = baseFunctions(source, keys(source));
      }
      var chain = true,
          index = -1,
          isFunc = isFunction(object),
          length = methodNames.length;

      if (options === false) {
        chain = false;
      } else if (isObject(options) && 'chain' in options) {
        chain = options.chain;
      }
      while (++index < length) {
        var methodName = methodNames[index],
            func = source[methodName];

        object[methodName] = func;
        if (isFunc) {
          object.prototype[methodName] = (function(func) {
            return function() {
              var chainAll = this.__chain__;
              if (chain || chainAll) {
                var result = object(this.__wrapped__),
                    actions = result.__actions__ = arrayCopy(this.__actions__);

                actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
                result.__chain__ = chainAll;
                return result;
              }
              return func.apply(object, arrayPush([this.value()], arguments));
            };
          }(func));
        }
      }
      return object;
    }

    /**
     * Reverts the `_` variable to its previous value and returns a reference to
     * the `lodash` function.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @returns {Function} Returns the `lodash` function.
     * @example
     *
     * var lodash = _.noConflict();
     */
    function noConflict() {
      root._ = oldDash;
      return this;
    }

    /**
     * A no-operation function that returns `undefined` regardless of the
     * arguments it receives.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @example
     *
     * var object = { 'user': 'fred' };
     *
     * _.noop(object) === undefined;
     * // => true
     */
    function noop() {
      // No operation performed.
    }

    /**
     * Creates a function that returns the property value at `path` on a
     * given object.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {Array|string} path The path of the property to get.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var objects = [
     *   { 'a': { 'b': { 'c': 2 } } },
     *   { 'a': { 'b': { 'c': 1 } } }
     * ];
     *
     * _.map(objects, _.property('a.b.c'));
     * // => [2, 1]
     *
     * _.pluck(_.sortBy(objects, _.property(['a', 'b', 'c'])), 'a.b.c');
     * // => [1, 2]
     */
    function property(path) {
      return isKey(path) ? baseProperty(path) : basePropertyDeep(path);
    }

    /**
     * The opposite of `_.property`; this method creates a function that returns
     * the property value at a given path on `object`.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {Object} object The object to query.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var array = [0, 1, 2],
     *     object = { 'a': array, 'b': array, 'c': array };
     *
     * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
     * // => [2, 0]
     *
     * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
     * // => [2, 0]
     */
    function propertyOf(object) {
      return function(path) {
        return baseGet(object, toPath(path), path + '');
      };
    }

    /**
     * Creates an array of numbers (positive and/or negative) progressing from
     * `start` up to, but not including, `end`. If `end` is not specified it is
     * set to `start` with `start` then set to `0`. If `end` is less than `start`
     * a zero-length range is created unless a negative `step` is specified.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {number} [start=0] The start of the range.
     * @param {number} end The end of the range.
     * @param {number} [step=1] The value to increment or decrement by.
     * @returns {Array} Returns the new array of numbers.
     * @example
     *
     * _.range(4);
     * // => [0, 1, 2, 3]
     *
     * _.range(1, 5);
     * // => [1, 2, 3, 4]
     *
     * _.range(0, 20, 5);
     * // => [0, 5, 10, 15]
     *
     * _.range(0, -4, -1);
     * // => [0, -1, -2, -3]
     *
     * _.range(1, 4, 0);
     * // => [1, 1, 1]
     *
     * _.range(0);
     * // => []
     */
    function range(start, end, step) {
      if (step && isIterateeCall(start, end, step)) {
        end = step = undefined;
      }
      start = +start || 0;
      step = step == null ? 1 : (+step || 0);

      if (end == null) {
        end = start;
        start = 0;
      } else {
        end = +end || 0;
      }
      // Use `Array(length)` so engines like Chakra and V8 avoid slower modes.
      // See https://youtu.be/XAqIpGU8ZZk#t=17m25s for more details.
      var index = -1,
          length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
          result = Array(length);

      while (++index < length) {
        result[index] = start;
        start += step;
      }
      return result;
    }

    /**
     * Invokes the iteratee function `n` times, returning an array of the results
     * of each invocation. The `iteratee` is bound to `thisArg` and invoked with
     * one argument; (index).
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {number} n The number of times to invoke `iteratee`.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {Array} Returns the array of results.
     * @example
     *
     * var diceRolls = _.times(3, _.partial(_.random, 1, 6, false));
     * // => [3, 6, 4]
     *
     * _.times(3, function(n) {
     *   mage.castSpell(n);
     * });
     * // => invokes `mage.castSpell(n)` three times with `n` of `0`, `1`, and `2`
     *
     * _.times(3, function(n) {
     *   this.cast(n);
     * }, mage);
     * // => also invokes `mage.castSpell(n)` three times
     */
    function times(n, iteratee, thisArg) {
      n = nativeFloor(n);

      // Exit early to avoid a JSC JIT bug in Safari 8
      // where `Array(0)` is treated as `Array(1)`.
      if (n < 1 || !nativeIsFinite(n)) {
        return [];
      }
      var index = -1,
          result = Array(nativeMin(n, MAX_ARRAY_LENGTH));

      iteratee = bindCallback(iteratee, thisArg, 1);
      while (++index < n) {
        if (index < MAX_ARRAY_LENGTH) {
          result[index] = iteratee(index);
        } else {
          iteratee(index);
        }
      }
      return result;
    }

    /**
     * Generates a unique ID. If `prefix` is provided the ID is appended to it.
     *
     * @static
     * @memberOf _
     * @category Utility
     * @param {string} [prefix] The value to prefix the ID with.
     * @returns {string} Returns the unique ID.
     * @example
     *
     * _.uniqueId('contact_');
     * // => 'contact_104'
     *
     * _.uniqueId();
     * // => '105'
     */
    function uniqueId(prefix) {
      var id = ++idCounter;
      return baseToString(prefix) + id;
    }

    /*------------------------------------------------------------------------*/

    /**
     * Adds two numbers.
     *
     * @static
     * @memberOf _
     * @category Math
     * @param {number} augend The first number to add.
     * @param {number} addend The second number to add.
     * @returns {number} Returns the sum.
     * @example
     *
     * _.add(6, 4);
     * // => 10
     */
    function add(augend, addend) {
      return (+augend || 0) + (+addend || 0);
    }

    /**
     * Calculates `n` rounded up to `precision`.
     *
     * @static
     * @memberOf _
     * @category Math
     * @param {number} n The number to round up.
     * @param {number} [precision=0] The precision to round up to.
     * @returns {number} Returns the rounded up number.
     * @example
     *
     * _.ceil(4.006);
     * // => 5
     *
     * _.ceil(6.004, 2);
     * // => 6.01
     *
     * _.ceil(6040, -2);
     * // => 6100
     */
    var ceil = createRound('ceil');

    /**
     * Calculates `n` rounded down to `precision`.
     *
     * @static
     * @memberOf _
     * @category Math
     * @param {number} n The number to round down.
     * @param {number} [precision=0] The precision to round down to.
     * @returns {number} Returns the rounded down number.
     * @example
     *
     * _.floor(4.006);
     * // => 4
     *
     * _.floor(0.046, 2);
     * // => 0.04
     *
     * _.floor(4060, -2);
     * // => 4000
     */
    var floor = createRound('floor');

    /**
     * Gets the maximum value of `collection`. If `collection` is empty or falsey
     * `-Infinity` is returned. If an iteratee function is provided it is invoked
     * for each value in `collection` to generate the criterion by which the value
     * is ranked. The `iteratee` is bound to `thisArg` and invoked with three
     * arguments: (value, index, collection).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Math
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [iteratee] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {*} Returns the maximum value.
     * @example
     *
     * _.max([4, 2, 8, 6]);
     * // => 8
     *
     * _.max([]);
     * // => -Infinity
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36 },
     *   { 'user': 'fred',   'age': 40 }
     * ];
     *
     * _.max(users, function(chr) {
     *   return chr.age;
     * });
     * // => { 'user': 'fred', 'age': 40 }
     *
     * // using the `_.property` callback shorthand
     * _.max(users, 'age');
     * // => { 'user': 'fred', 'age': 40 }
     */
    var max = createExtremum(gt, NEGATIVE_INFINITY);

    /**
     * Gets the minimum value of `collection`. If `collection` is empty or falsey
     * `Infinity` is returned. If an iteratee function is provided it is invoked
     * for each value in `collection` to generate the criterion by which the value
     * is ranked. The `iteratee` is bound to `thisArg` and invoked with three
     * arguments: (value, index, collection).
     *
     * If a property name is provided for `iteratee` the created `_.property`
     * style callback returns the property value of the given element.
     *
     * If a value is also provided for `thisArg` the created `_.matchesProperty`
     * style callback returns `true` for elements that have a matching property
     * value, else `false`.
     *
     * If an object is provided for `iteratee` the created `_.matches` style
     * callback returns `true` for elements that have the properties of the given
     * object, else `false`.
     *
     * @static
     * @memberOf _
     * @category Math
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [iteratee] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {*} Returns the minimum value.
     * @example
     *
     * _.min([4, 2, 8, 6]);
     * // => 2
     *
     * _.min([]);
     * // => Infinity
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36 },
     *   { 'user': 'fred',   'age': 40 }
     * ];
     *
     * _.min(users, function(chr) {
     *   return chr.age;
     * });
     * // => { 'user': 'barney', 'age': 36 }
     *
     * // using the `_.property` callback shorthand
     * _.min(users, 'age');
     * // => { 'user': 'barney', 'age': 36 }
     */
    var min = createExtremum(lt, POSITIVE_INFINITY);

    /**
     * Calculates `n` rounded to `precision`.
     *
     * @static
     * @memberOf _
     * @category Math
     * @param {number} n The number to round.
     * @param {number} [precision=0] The precision to round to.
     * @returns {number} Returns the rounded number.
     * @example
     *
     * _.round(4.006);
     * // => 4
     *
     * _.round(4.006, 2);
     * // => 4.01
     *
     * _.round(4060, -2);
     * // => 4100
     */
    var round = createRound('round');

    /**
     * Gets the sum of the values in `collection`.
     *
     * @static
     * @memberOf _
     * @category Math
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [iteratee] The function invoked per iteration.
     * @param {*} [thisArg] The `this` binding of `iteratee`.
     * @returns {number} Returns the sum.
     * @example
     *
     * _.sum([4, 6]);
     * // => 10
     *
     * _.sum({ 'a': 4, 'b': 6 });
     * // => 10
     *
     * var objects = [
     *   { 'n': 4 },
     *   { 'n': 6 }
     * ];
     *
     * _.sum(objects, function(object) {
     *   return object.n;
     * });
     * // => 10
     *
     * // using the `_.property` callback shorthand
     * _.sum(objects, 'n');
     * // => 10
     */
    function sum(collection, iteratee, thisArg) {
      if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
        iteratee = undefined;
      }
      iteratee = getCallback(iteratee, thisArg, 3);
      return iteratee.length == 1
        ? arraySum(isArray(collection) ? collection : toIterable(collection), iteratee)
        : baseSum(collection, iteratee);
    }

    /*------------------------------------------------------------------------*/

    // Ensure wrappers are instances of `baseLodash`.
    lodash.prototype = baseLodash.prototype;

    LodashWrapper.prototype = baseCreate(baseLodash.prototype);
    LodashWrapper.prototype.constructor = LodashWrapper;

    LazyWrapper.prototype = baseCreate(baseLodash.prototype);
    LazyWrapper.prototype.constructor = LazyWrapper;

    // Add functions to the `Map` cache.
    MapCache.prototype['delete'] = mapDelete;
    MapCache.prototype.get = mapGet;
    MapCache.prototype.has = mapHas;
    MapCache.prototype.set = mapSet;

    // Add functions to the `Set` cache.
    SetCache.prototype.push = cachePush;

    // Assign cache to `_.memoize`.
    memoize.Cache = MapCache;

    // Add functions that return wrapped values when chaining.
    lodash.after = after;
    lodash.ary = ary;
    lodash.assign = assign;
    lodash.at = at;
    lodash.before = before;
    lodash.bind = bind;
    lodash.bindAll = bindAll;
    lodash.bindKey = bindKey;
    lodash.callback = callback;
    lodash.chain = chain;
    lodash.chunk = chunk;
    lodash.compact = compact;
    lodash.constant = constant;
    lodash.countBy = countBy;
    lodash.create = create;
    lodash.curry = curry;
    lodash.curryRight = curryRight;
    lodash.debounce = debounce;
    lodash.defaults = defaults;
    lodash.defaultsDeep = defaultsDeep;
    lodash.defer = defer;
    lodash.delay = delay;
    lodash.difference = difference;
    lodash.drop = drop;
    lodash.dropRight = dropRight;
    lodash.dropRightWhile = dropRightWhile;
    lodash.dropWhile = dropWhile;
    lodash.fill = fill;
    lodash.filter = filter;
    lodash.flatten = flatten;
    lodash.flattenDeep = flattenDeep;
    lodash.flow = flow;
    lodash.flowRight = flowRight;
    lodash.forEach = forEach;
    lodash.forEachRight = forEachRight;
    lodash.forIn = forIn;
    lodash.forInRight = forInRight;
    lodash.forOwn = forOwn;
    lodash.forOwnRight = forOwnRight;
    lodash.functions = functions;
    lodash.groupBy = groupBy;
    lodash.indexBy = indexBy;
    lodash.initial = initial;
    lodash.intersection = intersection;
    lodash.invert = invert;
    lodash.invoke = invoke;
    lodash.keys = keys;
    lodash.keysIn = keysIn;
    lodash.map = map;
    lodash.mapKeys = mapKeys;
    lodash.mapValues = mapValues;
    lodash.matches = matches;
    lodash.matchesProperty = matchesProperty;
    lodash.memoize = memoize;
    lodash.merge = merge;
    lodash.method = method;
    lodash.methodOf = methodOf;
    lodash.mixin = mixin;
    lodash.modArgs = modArgs;
    lodash.negate = negate;
    lodash.omit = omit;
    lodash.once = once;
    lodash.pairs = pairs;
    lodash.partial = partial;
    lodash.partialRight = partialRight;
    lodash.partition = partition;
    lodash.pick = pick;
    lodash.pluck = pluck;
    lodash.property = property;
    lodash.propertyOf = propertyOf;
    lodash.pull = pull;
    lodash.pullAt = pullAt;
    lodash.range = range;
    lodash.rearg = rearg;
    lodash.reject = reject;
    lodash.remove = remove;
    lodash.rest = rest;
    lodash.restParam = restParam;
    lodash.set = set;
    lodash.shuffle = shuffle;
    lodash.slice = slice;
    lodash.sortBy = sortBy;
    lodash.sortByAll = sortByAll;
    lodash.sortByOrder = sortByOrder;
    lodash.spread = spread;
    lodash.take = take;
    lodash.takeRight = takeRight;
    lodash.takeRightWhile = takeRightWhile;
    lodash.takeWhile = takeWhile;
    lodash.tap = tap;
    lodash.throttle = throttle;
    lodash.thru = thru;
    lodash.times = times;
    lodash.toArray = toArray;
    lodash.toPlainObject = toPlainObject;
    lodash.transform = transform;
    lodash.union = union;
    lodash.uniq = uniq;
    lodash.unzip = unzip;
    lodash.unzipWith = unzipWith;
    lodash.values = values;
    lodash.valuesIn = valuesIn;
    lodash.where = where;
    lodash.without = without;
    lodash.wrap = wrap;
    lodash.xor = xor;
    lodash.zip = zip;
    lodash.zipObject = zipObject;
    lodash.zipWith = zipWith;

    // Add aliases.
    lodash.backflow = flowRight;
    lodash.collect = map;
    lodash.compose = flowRight;
    lodash.each = forEach;
    lodash.eachRight = forEachRight;
    lodash.extend = assign;
    lodash.iteratee = callback;
    lodash.methods = functions;
    lodash.object = zipObject;
    lodash.select = filter;
    lodash.tail = rest;
    lodash.unique = uniq;

    // Add functions to `lodash.prototype`.
    mixin(lodash, lodash);

    /*------------------------------------------------------------------------*/

    // Add functions that return unwrapped values when chaining.
    lodash.add = add;
    lodash.attempt = attempt;
    lodash.camelCase = camelCase;
    lodash.capitalize = capitalize;
    lodash.ceil = ceil;
    lodash.clone = clone;
    lodash.cloneDeep = cloneDeep;
    lodash.deburr = deburr;
    lodash.endsWith = endsWith;
    lodash.escape = escape;
    lodash.escapeRegExp = escapeRegExp;
    lodash.every = every;
    lodash.find = find;
    lodash.findIndex = findIndex;
    lodash.findKey = findKey;
    lodash.findLast = findLast;
    lodash.findLastIndex = findLastIndex;
    lodash.findLastKey = findLastKey;
    lodash.findWhere = findWhere;
    lodash.first = first;
    lodash.floor = floor;
    lodash.get = get;
    lodash.gt = gt;
    lodash.gte = gte;
    lodash.has = has;
    lodash.identity = identity;
    lodash.includes = includes;
    lodash.indexOf = indexOf;
    lodash.inRange = inRange;
    lodash.isArguments = isArguments;
    lodash.isArray = isArray;
    lodash.isBoolean = isBoolean;
    lodash.isDate = isDate;
    lodash.isElement = isElement;
    lodash.isEmpty = isEmpty;
    lodash.isEqual = isEqual;
    lodash.isError = isError;
    lodash.isFinite = isFinite;
    lodash.isFunction = isFunction;
    lodash.isMatch = isMatch;
    lodash.isNaN = isNaN;
    lodash.isNative = isNative;
    lodash.isNull = isNull;
    lodash.isNumber = isNumber;
    lodash.isObject = isObject;
    lodash.isPlainObject = isPlainObject;
    lodash.isRegExp = isRegExp;
    lodash.isString = isString;
    lodash.isTypedArray = isTypedArray;
    lodash.isUndefined = isUndefined;
    lodash.kebabCase = kebabCase;
    lodash.last = last;
    lodash.lastIndexOf = lastIndexOf;
    lodash.lt = lt;
    lodash.lte = lte;
    lodash.max = max;
    lodash.min = min;
    lodash.noConflict = noConflict;
    lodash.noop = noop;
    lodash.now = now;
    lodash.pad = pad;
    lodash.padLeft = padLeft;
    lodash.padRight = padRight;
    lodash.parseInt = parseInt;
    lodash.random = random;
    lodash.reduce = reduce;
    lodash.reduceRight = reduceRight;
    lodash.repeat = repeat;
    lodash.result = result;
    lodash.round = round;
    lodash.runInContext = runInContext;
    lodash.size = size;
    lodash.snakeCase = snakeCase;
    lodash.some = some;
    lodash.sortedIndex = sortedIndex;
    lodash.sortedLastIndex = sortedLastIndex;
    lodash.startCase = startCase;
    lodash.startsWith = startsWith;
    lodash.sum = sum;
    lodash.template = template;
    lodash.trim = trim;
    lodash.trimLeft = trimLeft;
    lodash.trimRight = trimRight;
    lodash.trunc = trunc;
    lodash.unescape = unescape;
    lodash.uniqueId = uniqueId;
    lodash.words = words;

    // Add aliases.
    lodash.all = every;
    lodash.any = some;
    lodash.contains = includes;
    lodash.eq = isEqual;
    lodash.detect = find;
    lodash.foldl = reduce;
    lodash.foldr = reduceRight;
    lodash.head = first;
    lodash.include = includes;
    lodash.inject = reduce;

    mixin(lodash, (function() {
      var source = {};
      baseForOwn(lodash, function(func, methodName) {
        if (!lodash.prototype[methodName]) {
          source[methodName] = func;
        }
      });
      return source;
    }()), false);

    /*------------------------------------------------------------------------*/

    // Add functions capable of returning wrapped and unwrapped values when chaining.
    lodash.sample = sample;

    lodash.prototype.sample = function(n) {
      if (!this.__chain__ && n == null) {
        return sample(this.value());
      }
      return this.thru(function(value) {
        return sample(value, n);
      });
    };

    /*------------------------------------------------------------------------*/

    /**
     * The semantic version number.
     *
     * @static
     * @memberOf _
     * @type string
     */
    lodash.VERSION = VERSION;

    // Assign default placeholders.
    arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
      lodash[methodName].placeholder = lodash;
    });

    // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
    arrayEach(['drop', 'take'], function(methodName, index) {
      LazyWrapper.prototype[methodName] = function(n) {
        var filtered = this.__filtered__;
        if (filtered && !index) {
          return new LazyWrapper(this);
        }
        n = n == null ? 1 : nativeMax(nativeFloor(n) || 0, 0);

        var result = this.clone();
        if (filtered) {
          result.__takeCount__ = nativeMin(result.__takeCount__, n);
        } else {
          result.__views__.push({ 'size': n, 'type': methodName + (result.__dir__ < 0 ? 'Right' : '') });
        }
        return result;
      };

      LazyWrapper.prototype[methodName + 'Right'] = function(n) {
        return this.reverse()[methodName](n).reverse();
      };
    });

    // Add `LazyWrapper` methods that accept an `iteratee` value.
    arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
      var type = index + 1,
          isFilter = type != LAZY_MAP_FLAG;

      LazyWrapper.prototype[methodName] = function(iteratee, thisArg) {
        var result = this.clone();
        result.__iteratees__.push({ 'iteratee': getCallback(iteratee, thisArg, 1), 'type': type });
        result.__filtered__ = result.__filtered__ || isFilter;
        return result;
      };
    });

    // Add `LazyWrapper` methods for `_.first` and `_.last`.
    arrayEach(['first', 'last'], function(methodName, index) {
      var takeName = 'take' + (index ? 'Right' : '');

      LazyWrapper.prototype[methodName] = function() {
        return this[takeName](1).value()[0];
      };
    });

    // Add `LazyWrapper` methods for `_.initial` and `_.rest`.
    arrayEach(['initial', 'rest'], function(methodName, index) {
      var dropName = 'drop' + (index ? '' : 'Right');

      LazyWrapper.prototype[methodName] = function() {
        return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
      };
    });

    // Add `LazyWrapper` methods for `_.pluck` and `_.where`.
    arrayEach(['pluck', 'where'], function(methodName, index) {
      var operationName = index ? 'filter' : 'map',
          createCallback = index ? baseMatches : property;

      LazyWrapper.prototype[methodName] = function(value) {
        return this[operationName](createCallback(value));
      };
    });

    LazyWrapper.prototype.compact = function() {
      return this.filter(identity);
    };

    LazyWrapper.prototype.reject = function(predicate, thisArg) {
      predicate = getCallback(predicate, thisArg, 1);
      return this.filter(function(value) {
        return !predicate(value);
      });
    };

    LazyWrapper.prototype.slice = function(start, end) {
      start = start == null ? 0 : (+start || 0);

      var result = this;
      if (result.__filtered__ && (start > 0 || end < 0)) {
        return new LazyWrapper(result);
      }
      if (start < 0) {
        result = result.takeRight(-start);
      } else if (start) {
        result = result.drop(start);
      }
      if (end !== undefined) {
        end = (+end || 0);
        result = end < 0 ? result.dropRight(-end) : result.take(end - start);
      }
      return result;
    };

    LazyWrapper.prototype.takeRightWhile = function(predicate, thisArg) {
      return this.reverse().takeWhile(predicate, thisArg).reverse();
    };

    LazyWrapper.prototype.toArray = function() {
      return this.take(POSITIVE_INFINITY);
    };

    // Add `LazyWrapper` methods to `lodash.prototype`.
    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
      var checkIteratee = /^(?:filter|map|reject)|While$/.test(methodName),
          retUnwrapped = /^(?:first|last)$/.test(methodName),
          lodashFunc = lodash[retUnwrapped ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName];

      if (!lodashFunc) {
        return;
      }
      lodash.prototype[methodName] = function() {
        var args = retUnwrapped ? [1] : arguments,
            chainAll = this.__chain__,
            value = this.__wrapped__,
            isHybrid = !!this.__actions__.length,
            isLazy = value instanceof LazyWrapper,
            iteratee = args[0],
            useLazy = isLazy || isArray(value);

        if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
          // Avoid lazy use if the iteratee has a "length" value other than `1`.
          isLazy = useLazy = false;
        }
        var interceptor = function(value) {
          return (retUnwrapped && chainAll)
            ? lodashFunc(value, 1)[0]
            : lodashFunc.apply(undefined, arrayPush([value], args));
        };

        var action = { 'func': thru, 'args': [interceptor], 'thisArg': undefined },
            onlyLazy = isLazy && !isHybrid;

        if (retUnwrapped && !chainAll) {
          if (onlyLazy) {
            value = value.clone();
            value.__actions__.push(action);
            return func.call(value);
          }
          return lodashFunc.call(undefined, this.value())[0];
        }
        if (!retUnwrapped && useLazy) {
          value = onlyLazy ? value : new LazyWrapper(this);
          var result = func.apply(value, args);
          result.__actions__.push(action);
          return new LodashWrapper(result, chainAll);
        }
        return this.thru(interceptor);
      };
    });

    // Add `Array` and `String` methods to `lodash.prototype`.
    arrayEach(['join', 'pop', 'push', 'replace', 'shift', 'sort', 'splice', 'split', 'unshift'], function(methodName) {
      var func = (/^(?:replace|split)$/.test(methodName) ? stringProto : arrayProto)[methodName],
          chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
          retUnwrapped = /^(?:join|pop|replace|shift)$/.test(methodName);

      lodash.prototype[methodName] = function() {
        var args = arguments;
        if (retUnwrapped && !this.__chain__) {
          return func.apply(this.value(), args);
        }
        return this[chainName](function(value) {
          return func.apply(value, args);
        });
      };
    });

    // Map minified function names to their real names.
    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
      var lodashFunc = lodash[methodName];
      if (lodashFunc) {
        var key = lodashFunc.name,
            names = realNames[key] || (realNames[key] = []);

        names.push({ 'name': methodName, 'func': lodashFunc });
      }
    });

    realNames[createHybridWrapper(undefined, BIND_KEY_FLAG).name] = [{ 'name': 'wrapper', 'func': undefined }];

    // Add functions to the lazy wrapper.
    LazyWrapper.prototype.clone = lazyClone;
    LazyWrapper.prototype.reverse = lazyReverse;
    LazyWrapper.prototype.value = lazyValue;

    // Add chaining functions to the `lodash` wrapper.
    lodash.prototype.chain = wrapperChain;
    lodash.prototype.commit = wrapperCommit;
    lodash.prototype.concat = wrapperConcat;
    lodash.prototype.plant = wrapperPlant;
    lodash.prototype.reverse = wrapperReverse;
    lodash.prototype.toString = wrapperToString;
    lodash.prototype.run = lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;

    // Add function aliases to the `lodash` wrapper.
    lodash.prototype.collect = lodash.prototype.map;
    lodash.prototype.head = lodash.prototype.first;
    lodash.prototype.select = lodash.prototype.filter;
    lodash.prototype.tail = lodash.prototype.rest;

    return lodash;
  }

  /*--------------------------------------------------------------------------*/

  // Export lodash.
  var _ = runInContext();

  // Some AMD build optimizers like r.js check for condition patterns like the following:
  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
    // Expose lodash to the global object when an AMD loader is present to avoid
    // errors in cases where lodash is loaded by a script tag and not intended
    // as an AMD module. See http://requirejs.org/docs/errors.html#mismatch for
    // more details.
    root._ = _;

    // Define as an anonymous module so, through path mapping, it can be
    // referenced as the "underscore" module.
    define(function() {
      return _;
    });
  }
  // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
  else if (freeExports && freeModule) {
    // Export for Node.js or RingoJS.
    if (moduleExports) {
      (freeModule.exports = _)._ = _;
    }
    // Export for Rhino with CommonJS support.
    else {
      freeExports._ = _;
    }
  }
  else {
    // Export for a browser or Rhino.
    root._ = _;
  }
}.call(this));

},{}],5:[function(require,module,exports){
/**
 * machina - A library for creating powerful and flexible finite state machines. Loosely inspired by Erlang/OTP's gen_fsm behavior.
 * Author: Jim Cowart (http://ifandelse.com)
 * Version: v1.1.2
 * Url: http://machina-js.org/
 * License(s): MIT, GPL
 */

( function( root, factory ) {
	/* istanbul ignore if  */
	if ( typeof define === "function" && define.amd ) {
		// AMD. Register as an anonymous module.
		define( [ "lodash" ], function( _ ) {
			return factory( _, root );
		} );
	/* istanbul ignore else  */
	} else if ( typeof module === "object" && module.exports ) {
		// Node, or CommonJS-Like environments
		module.exports = factory( require( "lodash" ) );
	} else {
		// Browser globals
		root.machina = factory( root._, root );
	}
}( this, function( _, global, undefined ) {
	var slice = [].slice;
	var NEXT_TRANSITION = "transition";
	var HANDLING = "handling";
	var HANDLED = "handled";
	var NO_HANDLER = "nohandler";
	var TRANSITION = "transition";
	var INVALID_STATE = "invalidstate";
	var DEFERRED = "deferred";
	var NEW_FSM = "newfsm";

	function getDefaultBehavioralOptions() {
	return {
		initialState: "uninitialized",
		eventListeners: {
			"*": []
		},
		states: {},
		namespace: utils.makeFsmNamespace(),
		useSafeEmit: false,
		hierarchy: {},
		pendingDelegations: {}
	};
}

	function getDefaultClientMeta() {
	return {
		inputQueue: [],
		targetReplayState: "",
		state: undefined,
		priorState: undefined,
		priorAction: "",
		currentAction: "",
		currentActionArgs: undefined,
		inExitHandler: false
	};
}

	function getLeaklessArgs( args, startIdx ) {
	var result = [];
	for ( var i = ( startIdx || 0 ); i < args.length; i++ ) {
		result[ i ] = args[ i ];
	}
	return result;
}

	function getChildFsmInstance( config ) {
	if ( !config ) {
		return;
	}
	var childFsmDefinition = {};
	if ( typeof config === "object" ) {
		// is this a config object with a factory?
		if ( config.factory ) {
			childFsmDefinition = config;
		} else {
			// assuming this is a machina instance
			childFsmDefinition.factory = function() {
				return config;
			};
		}
	} else if ( typeof config === "function" ) {
		childFsmDefinition.factory = config;
	}
	childFsmDefinition.instance = childFsmDefinition.factory();
	return childFsmDefinition;
}

	function listenToChild( fsm, child ) {
	return child.on( "*", function( eventName, data ) {
		switch ( eventName ) {
			case "nohandler":
				if ( !data.ticket && !data.delegated && data.namespace !== fsm.namespace ) {
					// Ok - we're dealing w/ a child handling input that should bubble up
					data.args[ 1 ].bubbling = true;
				}
				// we do NOT bubble _reset inputs up to the parent
				if ( data.inputType !== "_reset" ) {
					fsm.handle.apply( fsm, data.args );
				}
				break;
			case "handling" :
				var ticket = data.ticket;
				if ( ticket && fsm.pendingDelegations[ ticket ] ) {
					delete fsm.pendingDelegations[ ticket ];
				}
				fsm.emit( eventName, data ); // possibly transform payload?
				break;
			default:
				fsm.emit( eventName, data ); // possibly transform payload?
				break;
		}
	} );
}

	// _machKeys are members we want to track across the prototype chain of an extended FSM constructor
	// Since we want to eventually merge the aggregate of those values onto the instance so that FSMs
	// that share the same extended prototype won't share state *on* those prototypes.
	var _machKeys = [ "states", "initialState" ];
	var extend = function( protoProps, staticProps ) {
	var parent = this;
	var fsm; // placeholder for instance constructor
	var machObj = {}; // object used to hold initialState & states from prototype for instance-level merging
	var Ctor = function() {}; // placeholder ctor function used to insert level in prototype chain

	// The constructor function for the new subclass is either defined by you
	// (the "constructor" property in your `extend` definition), or defaulted
	// by us to simply call the parent's constructor.
	if ( protoProps && protoProps.hasOwnProperty( "constructor" ) ) {
		fsm = protoProps.constructor;
	} else {
		// The default machina constructor (when using inheritance) creates a
		// deep copy of the states/initialState values from the prototype and
		// extends them over the instance so that they'll be instance-level.
		// If an options arg (args[0]) is passed in, a states or intialState
		// value will be preferred over any data pulled up from the prototype.
		fsm = function() {
			var args = slice.call( arguments, 0 );
			args[ 0 ] = args[ 0 ] || {};
			var blendedState;
			var instanceStates = args[ 0 ].states || {};
			blendedState = _.merge( _.cloneDeep( machObj ), { states: instanceStates } );
			blendedState.initialState = args[ 0 ].initialState || this.initialState;
			_.extend( args[ 0 ], blendedState );
			parent.apply( this, args );
		};
	}

	// Inherit class (static) properties from parent.
	_.merge( fsm, parent );

	// Set the prototype chain to inherit from `parent`, without calling
	// `parent`'s constructor function.
	Ctor.prototype = parent.prototype;
	fsm.prototype = new Ctor();

	// Add prototype properties (instance properties) to the subclass,
	// if supplied.
	if ( protoProps ) {
		_.extend( fsm.prototype, protoProps );
		_.merge( machObj, _.transform( protoProps, function( accum, val, key ) {
			if ( _machKeys.indexOf( key ) !== -1 ) {
				accum[ key ] = val;
			}
		} ) );
	}

	// Add static properties to the constructor function, if supplied.
	if ( staticProps ) {
		_.merge( fsm, staticProps );
	}

	// Correctly set child's `prototype.constructor`.
	fsm.prototype.constructor = fsm;

	// Set a convenience property in case the parent's prototype is needed later.
	fsm.__super__ = parent.prototype;
	return fsm;
};

	function createUUID() {
	var s = [];
	var hexDigits = "0123456789abcdef";
	for ( var i = 0; i < 36; i++ ) {
		s[ i ] = hexDigits.substr( Math.floor( Math.random() * 0x10 ), 1 );
	}
	s[ 14 ] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
	
	s[ 19 ] = hexDigits.substr( ( s[ 19 ] & 0x3 ) | 0x8, 1 ); // bits 6-7 of the clock_seq_hi_and_reserved to 01
	
	s[ 8 ] = s[ 13 ] = s[ 18 ] = s[ 23 ] = "-";
	return s.join( "" );
}

	var utils = {
	makeFsmNamespace: ( function() {
		var machinaCount = 0;
		return function() {
			return "fsm." + machinaCount++;
		};
	} )(),
	listenToChild: listenToChild,
	getLeaklessArgs: getLeaklessArgs,
	getDefaultOptions: getDefaultBehavioralOptions,
	getDefaultClientMeta: getDefaultClientMeta,
	createUUID: createUUID
};

	var emitter = {

		emit: function( eventName ) {
		var args = getLeaklessArgs( arguments );
		if ( this.eventListeners[ "*" ] ) {
			_.each( this.eventListeners[ "*" ], function( callback ) {
				if ( !this.useSafeEmit ) {
					callback.apply( this, args );
				} else {
					try {
						callback.apply( this, args );
					} catch ( exception ) {
						/* istanbul ignore else  */
						if ( console && typeof console.log !== "undefined" ) {
							console.log( exception.stack );
						}
					}
				}
			}, this );
		}
		if ( this.eventListeners[ eventName ] ) {
			_.each( this.eventListeners[ eventName ], function( callback ) {
				if ( !this.useSafeEmit ) {
					callback.apply( this, args.slice( 1 ) );
				} else {
					try {
						callback.apply( this, args.slice( 1 ) );
					} catch ( exception ) {
						/* istanbul ignore else  */
						if ( console && typeof console.log !== "undefined" ) {
							console.log( exception.stack );
						}
					}
				}
			}, this );
		}
	},

		on: function( eventName, callback ) {
		var self = this;
		self.eventListeners = self.eventListeners || { "*": [] };
		if ( !self.eventListeners[ eventName ] ) {
			self.eventListeners[ eventName ] = [];
		}
		self.eventListeners[ eventName ].push( callback );
		return {
			eventName: eventName,
			callback: callback,
			off: function() {
				self.off( eventName, callback );
			}
		};
	},

		off: function( eventName, callback ) {
		this.eventListeners = this.eventListeners || { "*": [] };
		if ( !eventName ) {
			this.eventListeners = {};
		} else {
			if ( callback ) {
				this.eventListeners[ eventName ] = _.without( this.eventListeners[ eventName ], callback );
			} else {
				this.eventListeners[ eventName ] = [];
			}
		}
	}
	};

	var MACHINA_PROP = "__machina__";

	function BehavioralFsm( options ) {
	_.extend( this, options );
	_.defaults( this, getDefaultBehavioralOptions() );
	this.initialize.apply( this, arguments );
	machina.emit( NEW_FSM, this );
}

	_.extend( BehavioralFsm.prototype, {
	initialize: function() {},

	initClient: function initClient( client ) {
		var initialState = this.initialState;
		if ( !initialState ) {
			throw new Error( "You must specify an initial state for this FSM" );
		}
		if ( !this.states[ initialState ] ) {
			throw new Error( "The initial state specified does not exist in the states object." );
		}
		this.transition( client, initialState );
	},

	ensureClientMeta: function ensureClientMeta( client ) {
		if ( typeof client !== "object" ) {
			throw new Error( "An FSM client must be an object." );
		}
		client[ MACHINA_PROP ] = client[ MACHINA_PROP ] || {};
		if ( !client[ MACHINA_PROP ][ this.namespace ] ) {
			client[ MACHINA_PROP ][ this.namespace ] = _.cloneDeep( utils.getDefaultClientMeta() );
			this.initClient( client );
		}
		return client[ MACHINA_PROP ][ this.namespace ];
	},

	buildEventPayload: function( client, data ) {
		if ( _.isPlainObject( data ) ) {
			return _.extend( data, { client: client, namespace: this.namespace } );
		} else {
			return { client: client, data: data || null, namespace: this.namespace };
		}
	},

	getHandlerArgs: function( args, isCatchAll ) {
		// index 0 is the client, index 1 is inputType
		// if we're in a catch-all handler, input type needs to be included in the args
		// inputType might be an object, so we need to just get the inputType string if so
		var _args = args.slice( 0 );
		var input = _args[ 1 ];
		if ( typeof input === "object" ) {
			_args.splice( 1, 1, input.inputType );
		}
		return isCatchAll ?
			_args :
			[ _args[ 0 ] ].concat( _args.slice( 2 ) );
	},

	handle: function( client, input ) {
		var inputDef = input;
		if ( typeof input === "undefined" ) {
			throw new Error( "The input argument passed to the FSM's handle method is undefined. Did you forget to pass the input name?" );
		}
		if ( typeof input === "string" ) {
			inputDef = { inputType: input, delegated: false, ticket: undefined };
		}
		var clientMeta = this.ensureClientMeta( client );
		var args = getLeaklessArgs( arguments );
		if ( typeof input !== "object" ) {
			args.splice( 1, 1, inputDef );
		}
		clientMeta.currentActionArgs = args.slice( 1 );
		var currentState = clientMeta.state;
		var stateObj = this.states[ currentState ];
		var handlerName;
		var handler;
		var isCatchAll = false;
		var child;
		var result;
		var action;
		if ( !clientMeta.inExitHandler ) {
			child = stateObj._child && stateObj._child.instance;
			if ( child && !this.pendingDelegations[ inputDef.ticket ] && !inputDef.bubbling ) {
				inputDef.ticket = ( inputDef.ticket || utils.createUUID() );
				inputDef.delegated = true;
				this.pendingDelegations[ inputDef.ticket ] = { delegatedTo: child.namespace };
				// WARNING - returning a value from `handle` on child FSMs is not really supported.
				// If you need to return values from child FSM input handlers, use events instead.
				result = child.handle.apply( child, args );
			} else {
				if ( inputDef.ticket && this.pendingDelegations[ inputDef.ticket ] ) {
					delete this.pendingDelegations[ inputDef.ticket ];
				}
				handlerName = stateObj[ inputDef.inputType ] ? inputDef.inputType : "*";
				isCatchAll = ( handlerName === "*" );
				handler = ( stateObj[ handlerName ] || this[ handlerName ] ) || this[ "*" ];
				action = clientMeta.state + "." + handlerName;
				clientMeta.currentAction = action;
				var eventPayload = this.buildEventPayload(
					client,
					{ inputType: inputDef.inputType, delegated: inputDef.delegated, ticket: inputDef.ticket }
				);
				if ( !handler ) {
					this.emit( NO_HANDLER, _.extend( { args: args }, eventPayload ) );
				} else {
					this.emit( HANDLING, eventPayload );
					if ( typeof handler === "function" ) {
						result = handler.apply( this, this.getHandlerArgs( args, isCatchAll ) );
					} else {
						result = handler;
						this.transition( client, handler );
					}
					this.emit( HANDLED, eventPayload );
				}
				clientMeta.priorAction = clientMeta.currentAction;
				clientMeta.currentAction = "";
			}
		}
		return result;
	},

	transition: function( client, newState ) {
		var clientMeta = this.ensureClientMeta( client );
		var curState = clientMeta.state;
		var curStateObj = this.states[ curState ];
		var newStateObj = this.states[ newState ];
		var child;
		if ( !clientMeta.inExitHandler && newState !== curState ) {
			if ( newStateObj ) {
				if ( newStateObj._child ) {
					newStateObj._child = getChildFsmInstance( newStateObj._child );
					child = newStateObj._child && newStateObj._child.instance;
				}
				if ( curStateObj && curStateObj._onExit ) {
					clientMeta.inExitHandler = true;
					curStateObj._onExit.call( this, client );
					clientMeta.inExitHandler = false;
				}
				if ( curStateObj && curStateObj._child && curStateObj._child.instance && this.hierarchy[ curStateObj._child.instance.namespace ] ) {
					this.hierarchy[ curStateObj._child.instance.namespace ].off();
				}
				clientMeta.targetReplayState = newState;
				clientMeta.priorState = curState;
				clientMeta.state = newState;
				if ( child ) {
					this.hierarchy[ child.namespace ] = utils.listenToChild( this, child );
				}
				var eventPayload = this.buildEventPayload( client, {
					fromState: clientMeta.priorState,
					action: clientMeta.currentAction,
					toState: newState
				} );
				this.emit( TRANSITION, eventPayload );
				if ( newStateObj._onEnter ) {
					newStateObj._onEnter.call( this, client );
				}
				if ( child ) {
					child.handle( client, "_reset" );
				}

				if ( clientMeta.targetReplayState === newState ) {
					this.processQueue( client, NEXT_TRANSITION );
				}
				return;
			}
			this.emit( INVALID_STATE, this.buildEventPayload( client, {
				state: clientMeta.state,
				attemptedState: newState
			} ) );
		}
	},

	deferUntilTransition: function( client, stateName ) {
		var clientMeta = this.ensureClientMeta( client );
		if ( clientMeta.currentActionArgs ) {
			var queued = {
				type: NEXT_TRANSITION,
				untilState: stateName,
				args: clientMeta.currentActionArgs
			};
			clientMeta.inputQueue.push( queued );
			var eventPayload = this.buildEventPayload( client, {
				state: clientMeta.state,
				queuedArgs: queued
			} );
			this.emit( DEFERRED, eventPayload );
		}
	},

	deferAndTransition: function( client, stateName ) {
		this.deferUntilTransition( client, stateName );
		this.transition( client, stateName );
	},

	processQueue: function( client ) {
		var clientMeta = this.ensureClientMeta( client );
		var filterFn = function( item ) {
			return ( ( !item.untilState ) || ( item.untilState === clientMeta.state ) );
		};
		var toProcess = _.filter( clientMeta.inputQueue, filterFn );
		clientMeta.inputQueue = _.difference( clientMeta.inputQueue, toProcess );
		_.each( toProcess, function( item ) {
			this.handle.apply( this, [ client ].concat( item.args ) );
		}, this );
	},

	clearQueue: function( client, name ) {
		var clientMeta = this.ensureClientMeta( client );
		if ( !name ) {
			clientMeta.inputQueue = [];
		} else {
			var filter = function( evnt ) {
				return ( name ? evnt.untilState !== name : true );
			};
			clientMeta.inputQueue = _.filter( clientMeta.inputQueue, filter );
		}
	},

	compositeState: function( client ) {
		var clientMeta = this.ensureClientMeta( client );
		var state = clientMeta.state;
		var child = this.states[state]._child && this.states[state]._child.instance;
		if ( child ) {
			state += "." + child.compositeState( client );
		}
		return state;
	}
}, emitter );

	BehavioralFsm.extend = extend;

	var Fsm = {
	constructor: function() {
		BehavioralFsm.apply( this, arguments );
		this.ensureClientMeta();
	},
	initClient: function initClient() {
		var initialState = this.initialState;
		if ( !initialState ) {
			throw new Error( "You must specify an initial state for this FSM" );
		}
		if ( !this.states[ initialState ] ) {
			throw new Error( "The initial state specified does not exist in the states object." );
		}
		this.transition( initialState );
	},
	ensureClientMeta: function ensureClientMeta() {
		if ( !this._stamped ) {
			this._stamped = true;
			_.defaults( this, _.cloneDeep( getDefaultClientMeta() ) );
			this.initClient();
		}
		return this;
	},

	ensureClientArg: function( args ) {
		var _args = args;
		// we need to test the args and verify that if a client arg has
		// been passed, it must be this FSM instance (this isn't a behavioral FSM)
		if ( typeof _args[ 0 ] === "object" && !( "inputType" in _args[ 0 ] ) && _args[ 0 ] !== this ) {
			_args.splice( 0, 1, this );
		} else if ( typeof _args[ 0 ] !== "object" || ( typeof _args[ 0 ] === "object" && ( "inputType" in _args[ 0 ] ) ) ) {
			_args.unshift( this );
		}
		return _args;
	},

	getHandlerArgs: function( args, isCatchAll ) {
		// index 0 is the client, index 1 is inputType
		// if we're in a catch-all handler, input type needs to be included in the args
		// inputType might be an object, so we need to just get the inputType string if so
		var _args = args;
		var input = _args[ 1 ];
		if ( typeof inputType === "object" ) {
			_args.splice( 1, 1, input.inputType );
		}
		return isCatchAll ?
			_args.slice( 1 ) :
			_args.slice( 2 );
	},
	// "classic" machina FSM do not emit the client property on events (which would be the FSM itself)
	buildEventPayload: function() {
		var args = this.ensureClientArg( utils.getLeaklessArgs( arguments ) );
		var data = args[ 1 ];
		if ( _.isPlainObject( data ) ) {
			return _.extend( data, { namespace: this.namespace } );
		} else {
			return { data: data || null, namespace: this.namespace };
		}
	}
};

	_.each( [
		"handle",
		"transition",
		"deferUntilTransition",
		"processQueue",
		"clearQueue"
], function( methodWithClientInjected ) {
	Fsm[methodWithClientInjected] = function() {
		var args = this.ensureClientArg( utils.getLeaklessArgs( arguments ) );
		return BehavioralFsm.prototype[methodWithClientInjected].apply( this, args );
	};
} );
 
	Fsm = BehavioralFsm.extend( Fsm );

	var machina = _.merge( emitter, {
	Fsm: Fsm,
	BehavioralFsm: BehavioralFsm,
	utils: utils,
	eventListeners: {
		newFsm: []
	}
} );

	return machina;
} ) );

},{"lodash":6}],6:[function(require,module,exports){
module.exports=require(4)
},{}],7:[function(require,module,exports){
"use strict";
var Promise = require("./rsvp/promise")["default"];
var EventTarget = require("./rsvp/events")["default"];
var denodeify = require("./rsvp/node")["default"];
var all = require("./rsvp/all")["default"];
var allSettled = require("./rsvp/all-settled")["default"];
var race = require("./rsvp/race")["default"];
var hash = require("./rsvp/hash")["default"];
var hashSettled = require("./rsvp/hash-settled")["default"];
var rethrow = require("./rsvp/rethrow")["default"];
var defer = require("./rsvp/defer")["default"];
var config = require("./rsvp/config").config;
var configure = require("./rsvp/config").configure;
var map = require("./rsvp/map")["default"];
var resolve = require("./rsvp/resolve")["default"];
var reject = require("./rsvp/reject")["default"];
var filter = require("./rsvp/filter")["default"];
var asap = require("./rsvp/asap")["default"];

config.async = asap; // default async is asap;

function async(callback, arg) {
  config.async(callback, arg);
}

function on() {
  config.on.apply(config, arguments);
}

function off() {
  config.off.apply(config, arguments);
}

// Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ === 'object') {
  var callbacks = window.__PROMISE_INSTRUMENTATION__;
  configure('instrument', true);
  for (var eventName in callbacks) {
    if (callbacks.hasOwnProperty(eventName)) {
      on(eventName, callbacks[eventName]);
    }
  }
}

exports.Promise = Promise;
exports.EventTarget = EventTarget;
exports.all = all;
exports.allSettled = allSettled;
exports.race = race;
exports.hash = hash;
exports.hashSettled = hashSettled;
exports.rethrow = rethrow;
exports.defer = defer;
exports.denodeify = denodeify;
exports.configure = configure;
exports.on = on;
exports.off = off;
exports.resolve = resolve;
exports.reject = reject;
exports.async = async;
exports.map = map;
exports.filter = filter;
},{"./rsvp/all":10,"./rsvp/all-settled":9,"./rsvp/asap":11,"./rsvp/config":12,"./rsvp/defer":13,"./rsvp/events":15,"./rsvp/filter":16,"./rsvp/hash":18,"./rsvp/hash-settled":17,"./rsvp/map":20,"./rsvp/node":21,"./rsvp/promise":23,"./rsvp/race":29,"./rsvp/reject":30,"./rsvp/resolve":31,"./rsvp/rethrow":32}],8:[function(require,module,exports){
'use strict';
var objectOrFunction = require('./utils').objectOrFunction;
var isFunction = require('./utils').isFunction;
var now = require('./utils').now;
var instrument = require('./instrument')['default'];
var config = require('./config').config;
function noop() {
}
var PENDING = void 0;
var FULFILLED = 1;
var REJECTED = 2;
var GET_THEN_ERROR = new ErrorObject();
function getThen(promise) {
    try {
        return promise.then;
    } catch (error) {
        GET_THEN_ERROR.error = error;
        return GET_THEN_ERROR;
    }
}
function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
    try {
        then.call(value, fulfillmentHandler, rejectionHandler);
    } catch (e) {
        return e;
    }
}
function handleForeignThenable(promise, thenable, then) {
    config.async(function (promise$2) {
        var sealed = false;
        var error = tryThen(then, thenable, function (value) {
                if (sealed) {
                    return;
                }
                sealed = true;
                if (thenable !== value) {
                    resolve(promise$2, value);
                } else {
                    fulfill(promise$2, value);
                }
            }, function (reason) {
                if (sealed) {
                    return;
                }
                sealed = true;
                reject(promise$2, reason);
            }, 'Settle: ' + (promise$2._label || ' unknown promise'));
        if (!sealed && error) {
            sealed = true;
            reject(promise$2, error);
        }
    }, promise);
}
function handleOwnThenable(promise, thenable) {
    promise._onerror = null;
    if (thenable._state === FULFILLED) {
        fulfill(promise, thenable._result);
    } else if (promise._state === REJECTED) {
        reject(promise, thenable._result);
    } else {
        subscribe(thenable, undefined, function (value) {
            if (thenable !== value) {
                resolve(promise, value);
            } else {
                fulfill(promise, value);
            }
        }, function (reason) {
            reject(promise, reason);
        });
    }
}
function handleMaybeThenable(promise, maybeThenable) {
    if (maybeThenable instanceof promise.constructor) {
        handleOwnThenable(promise, maybeThenable);
    } else {
        var then = getThen(maybeThenable);
        if (then === GET_THEN_ERROR) {
            reject(promise, GET_THEN_ERROR.error);
        } else if (then === undefined) {
            fulfill(promise, maybeThenable);
        } else if (isFunction(then)) {
            handleForeignThenable(promise, maybeThenable, then);
        } else {
            fulfill(promise, maybeThenable);
        }
    }
}
function resolve(promise, value) {
    if (promise === value) {
        fulfill(promise, value);
    } else if (objectOrFunction(value)) {
        handleMaybeThenable(promise, value);
    } else {
        fulfill(promise, value);
    }
}
function publishRejection(promise) {
    if (promise._onerror) {
        promise._onerror(promise._result);
    }
    publish(promise);
}
function fulfill(promise, value) {
    if (promise._state !== PENDING) {
        return;
    }
    promise._result = value;
    promise._state = FULFILLED;
    if (promise._subscribers.length === 0) {
        if (config.instrument) {
            instrument('fulfilled', promise);
        }
    } else {
        config.async(publish, promise);
    }
}
function reject(promise, reason) {
    if (promise._state !== PENDING) {
        return;
    }
    promise._state = REJECTED;
    promise._result = reason;
    config.async(publishRejection, promise);
}
function subscribe(parent, child, onFulfillment, onRejection) {
    var subscribers = parent._subscribers;
    var length = subscribers.length;
    parent._onerror = null;
    subscribers[length] = child;
    subscribers[length + FULFILLED] = onFulfillment;
    subscribers[length + REJECTED] = onRejection;
    if (length === 0 && parent._state) {
        config.async(publish, parent);
    }
}
function publish(promise) {
    var subscribers = promise._subscribers;
    var settled = promise._state;
    if (config.instrument) {
        instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
    }
    if (subscribers.length === 0) {
        return;
    }
    var child, callback, detail = promise._result;
    for (var i = 0; i < subscribers.length; i += 3) {
        child = subscribers[i];
        callback = subscribers[i + settled];
        if (child) {
            invokeCallback(settled, child, callback, detail);
        } else {
            callback(detail);
        }
    }
    promise._subscribers.length = 0;
}
function ErrorObject() {
    this.error = null;
}
var TRY_CATCH_ERROR = new ErrorObject();
function tryCatch(callback, detail) {
    try {
        return callback(detail);
    } catch (e) {
        TRY_CATCH_ERROR.error = e;
        return TRY_CATCH_ERROR;
    }
}
function invokeCallback(settled, promise, callback, detail) {
    var hasCallback = isFunction(callback), value, error, succeeded, failed;
    if (hasCallback) {
        value = tryCatch(callback, detail);
        if (value === TRY_CATCH_ERROR) {
            failed = true;
            error = value.error;
            value = null;
        } else {
            succeeded = true;
        }
        if (promise === value) {
            reject(promise, new TypeError('A promises callback cannot return that same promise.'));
            return;
        }
    } else {
        value = detail;
        succeeded = true;
    }
    if (promise._state !== PENDING) {
    }    // noop
    else if (hasCallback && succeeded) {
        resolve(promise, value);
    } else if (failed) {
        reject(promise, error);
    } else if (settled === FULFILLED) {
        fulfill(promise, value);
    } else if (settled === REJECTED) {
        reject(promise, value);
    }
}
function initializePromise(promise, resolver) {
    try {
        resolver(function resolvePromise(value) {
            resolve(promise, value);
        }, function rejectPromise(reason) {
            reject(promise, reason);
        });
    } catch (e) {
        reject(promise, e);
    }
}
exports.noop = noop;
exports.resolve = resolve;
exports.reject = reject;
exports.fulfill = fulfill;
exports.subscribe = subscribe;
exports.publish = publish;
exports.publishRejection = publishRejection;
exports.initializePromise = initializePromise;
exports.invokeCallback = invokeCallback;
exports.FULFILLED = FULFILLED;
exports.REJECTED = REJECTED;
},{"./config":12,"./instrument":19,"./utils":33}],9:[function(require,module,exports){
'use strict';
var Enumerator = require('./enumerator')['default'];
var makeSettledResult = require('./enumerator').makeSettledResult;
var Promise = require('./promise')['default'];
var o_create = require('./utils').o_create;
function AllSettled(Constructor, entries, label) {
    this._superConstructor(Constructor, entries, false, label);
}
AllSettled.prototype = o_create(Enumerator.prototype);
AllSettled.prototype._superConstructor = Enumerator;
AllSettled.prototype._makeResult = makeSettledResult;
AllSettled.prototype._validationError = function () {
    return new Error('allSettled must be called with an array');
};
/**
  `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
  a fail-fast method, it waits until all the promises have returned and
  shows you all the results. This is useful if you want to handle multiple
  promises' failure states together as a set.

  Returns a promise that is fulfilled when all the given promises have been
  settled. The return promise is fulfilled with an array of the states of
  the promises passed into the `promises` array argument.

  Each state object will either indicate fulfillment or rejection, and
  provide the corresponding value or reason. The states will take one of
  the following formats:

  ```javascript
  { state: 'fulfilled', value: value }
    or
  { state: 'rejected', reason: reason }
  ```

  Example:

  ```javascript
  var promise1 = RSVP.Promise.resolve(1);
  var promise2 = RSVP.Promise.reject(new Error('2'));
  var promise3 = RSVP.Promise.reject(new Error('3'));
  var promises = [ promise1, promise2, promise3 ];

  RSVP.allSettled(promises).then(function(array){
    // array == [
    //   { state: 'fulfilled', value: 1 },
    //   { state: 'rejected', reason: Error },
    //   { state: 'rejected', reason: Error }
    // ]
    // Note that for the second item, reason.message will be "2", and for the
    // third item, reason.message will be "3".
  }, function(error) {
    // Not run. (This block would only be called if allSettled had failed,
    // for instance if passed an incorrect argument type.)
  });
  ```

  @method allSettled
  @static
  @for RSVP
  @param {Array} promises
  @param {String} label - optional string that describes the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled with an array of the settled
  states of the constituent promises.
*/
exports['default'] = function allSettled(entries, label) {
    return new AllSettled(Promise, entries, label).promise;
};
},{"./enumerator":14,"./promise":23,"./utils":33}],10:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  This is a convenient alias for `RSVP.Promise.all`.

  @method all
  @static
  @for RSVP
  @param {Array} array Array of promises.
  @param {String} label An optional label. This is useful
  for tooling.
*/
exports['default'] = function all(array, label) {
    return Promise.all(array, label);
};
},{"./promise":23}],11:[function(require,module,exports){
var process=require("__browserify_process");'use strict';
var length = 0;
exports['default'] = function asap(callback, arg) {
    queue[length] = callback;
    queue[length + 1] = arg;
    length += 2;
    if (length === 2) {
        // If length is 1, that means that we need to schedule an async flush.
        // If additional callbacks are queued before the queue is flushed, they
        // will be processed by this flush that we are scheduling.
        scheduleFlush();
    }
};
var browserGlobal = typeof window !== 'undefined' ? window : {};
var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
// test for web worker but not in IE10
var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined';
// node
function useNextTick() {
    return function () {
        process.nextTick(flush);
    };
}
function useMutationObserver() {
    var iterations = 0;
    var observer = new BrowserMutationObserver(flush);
    var node = document.createTextNode('');
    observer.observe(node, { characterData: true });
    return function () {
        node.data = iterations = ++iterations % 2;
    };
}
// web worker
function useMessageChannel() {
    var channel = new MessageChannel();
    channel.port1.onmessage = flush;
    return function () {
        channel.port2.postMessage(0);
    };
}
function useSetTimeout() {
    return function () {
        setTimeout(flush, 1);
    };
}
var queue = new Array(1000);
function flush() {
    for (var i = 0; i < length; i += 2) {
        var callback = queue[i];
        var arg = queue[i + 1];
        callback(arg);
        queue[i] = undefined;
        queue[i + 1] = undefined;
    }
    length = 0;
}
var scheduleFlush;
// Decide what async method to use to triggering processing of queued callbacks:
if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
    scheduleFlush = useNextTick();
} else if (BrowserMutationObserver) {
    scheduleFlush = useMutationObserver();
} else if (isWorker) {
    scheduleFlush = useMessageChannel();
} else {
    scheduleFlush = useSetTimeout();
}
},{"__browserify_process":2}],12:[function(require,module,exports){
'use strict';
var EventTarget = require('./events')['default'];
var config = { instrument: false };
EventTarget.mixin(config);
function configure(name, value) {
    if (name === 'onerror') {
        // handle for legacy users that expect the actual
        // error to be passed to their function added via
        // `RSVP.configure('onerror', someFunctionHere);`
        config.on('error', value);
        return;
    }
    if (arguments.length === 2) {
        config[name] = value;
    } else {
        return config[name];
    }
}
exports.config = config;
exports.configure = configure;
},{"./events":15}],13:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  `RSVP.defer` returns an object similar to jQuery's `$.Deferred`.
  `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
  interface. New code should use the `RSVP.Promise` constructor instead.

  The object returned from `RSVP.defer` is a plain object with three properties:

  * promise - an `RSVP.Promise`.
  * reject - a function that causes the `promise` property on this object to
    become rejected
  * resolve - a function that causes the `promise` property on this object to
    become fulfilled.

  Example:

   ```javascript
   var deferred = RSVP.defer();

   deferred.resolve("Success!");

   defered.promise.then(function(value){
     // value here is "Success!"
   });
   ```

  @method defer
  @static
  @for RSVP
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @return {Object}
 */
exports['default'] = function defer(label) {
    var deferred = {};
    deferred.promise = new Promise(function (resolve, reject) {
        deferred.resolve = resolve;
        deferred.reject = reject;
    }, label);
    return deferred;
};
},{"./promise":23}],14:[function(require,module,exports){
'use strict';
var isArray = require('./utils').isArray;
var isMaybeThenable = require('./utils').isMaybeThenable;
var noop = require('./-internal').noop;
var reject = require('./-internal').reject;
var fulfill = require('./-internal').fulfill;
var subscribe = require('./-internal').subscribe;
var FULFILLED = require('./-internal').FULFILLED;
var REJECTED = require('./-internal').REJECTED;
var PENDING = require('./-internal').PENDING;
var ABORT_ON_REJECTION = true;
exports.ABORT_ON_REJECTION = ABORT_ON_REJECTION;
function makeSettledResult(state, position, value) {
    if (state === FULFILLED) {
        return {
            state: 'fulfilled',
            value: value
        };
    } else {
        return {
            state: 'rejected',
            reason: value
        };
    }
}
exports.makeSettledResult = makeSettledResult;
function Enumerator(Constructor, input, abortOnReject, label) {
    this._instanceConstructor = Constructor;
    this.promise = new Constructor(noop, label);
    this._abortOnReject = abortOnReject;
    if (this._validateInput(input)) {
        this._input = input;
        this.length = input.length;
        this._remaining = input.length;
        this._init();
        if (this.length === 0) {
            fulfill(this.promise, this._result);
        } else {
            this.length = this.length || 0;
            this._enumerate();
            if (this._remaining === 0) {
                fulfill(this.promise, this._result);
            }
        }
    } else {
        reject(this.promise, this._validationError());
    }
}
Enumerator.prototype._validateInput = function (input) {
    return isArray(input);
};
Enumerator.prototype._validationError = function () {
    return new Error('Array Methods must be provided an Array');
};
Enumerator.prototype._init = function () {
    this._result = new Array(this.length);
};
exports['default'] = Enumerator;
Enumerator.prototype._enumerate = function () {
    var length = this.length;
    var promise = this.promise;
    var input = this._input;
    for (var i = 0; promise._state === PENDING && i < length; i++) {
        this._eachEntry(input[i], i);
    }
};
Enumerator.prototype._eachEntry = function (entry, i) {
    var c = this._instanceConstructor;
    if (isMaybeThenable(entry)) {
        if (entry.constructor === c && entry._state !== PENDING) {
            entry._onerror = null;
            this._settledAt(entry._state, i, entry._result);
        } else {
            this._willSettleAt(c.resolve(entry), i);
        }
    } else {
        this._remaining--;
        this._result[i] = this._makeResult(FULFILLED, i, entry);
    }
};
Enumerator.prototype._settledAt = function (state, i, value) {
    var promise = this.promise;
    if (promise._state === PENDING) {
        this._remaining--;
        if (this._abortOnReject && state === REJECTED) {
            reject(promise, value);
        } else {
            this._result[i] = this._makeResult(state, i, value);
        }
    }
    if (this._remaining === 0) {
        fulfill(promise, this._result);
    }
};
Enumerator.prototype._makeResult = function (state, i, value) {
    return value;
};
Enumerator.prototype._willSettleAt = function (promise, i) {
    var enumerator = this;
    subscribe(promise, undefined, function (value) {
        enumerator._settledAt(FULFILLED, i, value);
    }, function (reason) {
        enumerator._settledAt(REJECTED, i, reason);
    });
};
},{"./-internal":8,"./utils":33}],15:[function(require,module,exports){
'use strict';
function indexOf(callbacks, callback) {
    for (var i = 0, l = callbacks.length; i < l; i++) {
        if (callbacks[i] === callback) {
            return i;
        }
    }
    return -1;
}
function callbacksFor(object) {
    var callbacks = object._promiseCallbacks;
    if (!callbacks) {
        callbacks = object._promiseCallbacks = {};
    }
    return callbacks;
}
/**
  @class RSVP.EventTarget
*/
exports['default'] = {
    mixin: function (object) {
        object.on = this.on;
        object.off = this.off;
        object.trigger = this.trigger;
        object._promiseCallbacks = undefined;
        return object;
    },
    on: function (eventName, callback) {
        var allCallbacks = callbacksFor(this), callbacks;
        callbacks = allCallbacks[eventName];
        if (!callbacks) {
            callbacks = allCallbacks[eventName] = [];
        }
        if (indexOf(callbacks, callback) === -1) {
            callbacks.push(callback);
        }
    },
    off: function (eventName, callback) {
        var allCallbacks = callbacksFor(this), callbacks, index;
        if (!callback) {
            allCallbacks[eventName] = [];
            return;
        }
        callbacks = allCallbacks[eventName];
        index = indexOf(callbacks, callback);
        if (index !== -1) {
            callbacks.splice(index, 1);
        }
    },
    trigger: function (eventName, options) {
        var allCallbacks = callbacksFor(this), callbacks, callbackTuple, callback, binding;
        if (callbacks = allCallbacks[eventName]) {
            // Don't cache the callbacks.length since it may grow
            for (var i = 0; i < callbacks.length; i++) {
                callback = callbacks[i];
                callback(options);
            }
        }
    }
};
},{}],16:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
var isFunction = require('./utils').isFunction;
var isMaybeThenable = require('./utils').isMaybeThenable;
/**
 `RSVP.filter` is similar to JavaScript's native `filter` method, except that it
  waits for all promises to become fulfilled before running the `filterFn` on
  each item in given to `promises`. `RSVP.filter` returns a promise that will
  become fulfilled with the result of running `filterFn` on the values the
  promises become fulfilled with.

  For example:

  ```javascript

  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.resolve(2);
  var promise3 = RSVP.resolve(3);

  var promises = [promise1, promise2, promise3];

  var filterFn = function(item){
    return item > 1;
  };

  RSVP.filter(promises, filterFn).then(function(result){
    // result is [ 2, 3 ]
  });
  ```

  If any of the `promises` given to `RSVP.filter` are rejected, the first promise
  that is rejected will be given as an argument to the returned promise's
  rejection handler. For example:

  ```javascript
  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.reject(new Error("2"));
  var promise3 = RSVP.reject(new Error("3"));
  var promises = [ promise1, promise2, promise3 ];

  var filterFn = function(item){
    return item > 1;
  };

  RSVP.filter(promises, filterFn).then(function(array){
    // Code here never runs because there are rejected promises!
  }, function(reason) {
    // reason.message === "2"
  });
  ```

  `RSVP.filter` will also wait for any promises returned from `filterFn`.
  For instance, you may want to fetch a list of users then return a subset
  of those users based on some asynchronous operation:

  ```javascript

  var alice = { name: 'alice' };
  var bob   = { name: 'bob' };
  var users = [ alice, bob ];

  var promises = users.map(function(user){
    return RSVP.resolve(user);
  });

  var filterFn = function(user){
    // Here, Alice has permissions to create a blog post, but Bob does not.
    return getPrivilegesForUser(user).then(function(privs){
      return privs.can_create_blog_post === true;
    });
  };
  RSVP.filter(promises, filterFn).then(function(users){
    // true, because the server told us only Alice can create a blog post.
    users.length === 1;
    // false, because Alice is the only user present in `users`
    users[0] === bob;
  });
  ```

  @method filter
  @static
  @for RSVP
  @param {Array} promises
  @param {Function} filterFn - function to be called on each resolved value to
  filter the final results.
  @param {String} label optional string describing the promise. Useful for
  tooling.
  @return {Promise}
*/
exports['default'] = function filter(promises, filterFn, label) {
    return Promise.all(promises, label).then(function (values) {
        if (!isFunction(filterFn)) {
            throw new TypeError('You must pass a function as filter\'s second argument.');
        }
        var length = values.length;
        var filtered = new Array(length);
        for (var i = 0; i < length; i++) {
            filtered[i] = filterFn(values[i]);
        }
        return Promise.all(filtered, label).then(function (filtered$2) {
            var results = new Array(length);
            var newLength = 0;
            for (var i$2 = 0; i$2 < length; i$2++) {
                if (filtered$2[i$2]) {
                    results[newLength] = values[i$2];
                    newLength++;
                }
            }
            results.length = newLength;
            return results;
        });
    });
};
},{"./promise":23,"./utils":33}],17:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
var makeSettledResult = require('./enumerator').makeSettledResult;
var PromiseHash = require('./promise-hash')['default'];
var Enumerator = require('./enumerator')['default'];
var o_create = require('./utils').o_create;
function HashSettled(Constructor, object, label) {
    this._superConstructor(Constructor, object, false, label);
}
HashSettled.prototype = o_create(PromiseHash.prototype);
HashSettled.prototype._superConstructor = Enumerator;
HashSettled.prototype._makeResult = makeSettledResult;
HashSettled.prototype._validationError = function () {
    return new Error('hashSettled must be called with an object');
};
/**
  `RSVP.hashSettled` is similar to `RSVP.allSettled`, but takes an object
  instead of an array for its `promises` argument.

  Unlike `RSVP.all` or `RSVP.hash`, which implement a fail-fast method,
  but like `RSVP.allSettled`, `hashSettled` waits until all the
  constituent promises have returned and then shows you all the results
  with their states and values/reasons. This is useful if you want to
  handle multiple promises' failure states together as a set.

  Returns a promise that is fulfilled when all the given promises have been
  settled, or rejected if the passed parameters are invalid.

  The returned promise is fulfilled with a hash that has the same key names as
  the `promises` object argument. If any of the values in the object are not
  promises, they will be copied over to the fulfilled object and marked with state
  'fulfilled'.

  Example:

  ```javascript
  var promises = {
    myPromise: RSVP.Promise.resolve(1),
    yourPromise: RSVP.Promise.resolve(2),
    theirPromise: RSVP.Promise.resolve(3),
    notAPromise: 4
  };

  RSVP.hashSettled(promises).then(function(hash){
    // hash here is an object that looks like:
    // {
    //   myPromise: { state: 'fulfilled', value: 1 },
    //   yourPromise: { state: 'fulfilled', value: 2 },
    //   theirPromise: { state: 'fulfilled', value: 3 },
    //   notAPromise: { state: 'fulfilled', value: 4 }
    // }
  });
  ```

  If any of the `promises` given to `RSVP.hash` are rejected, the state will
  be set to 'rejected' and the reason for rejection provided.

  Example:

  ```javascript
  var promises = {
    myPromise: RSVP.Promise.resolve(1),
    rejectedPromise: RSVP.Promise.reject(new Error('rejection')),
    anotherRejectedPromise: RSVP.Promise.reject(new Error('more rejection')),
  };

  RSVP.hashSettled(promises).then(function(hash){
    // hash here is an object that looks like:
    // {
    //   myPromise:              { state: 'fulfilled', value: 1 },
    //   rejectedPromise:        { state: 'rejected', reason: Error },
    //   anotherRejectedPromise: { state: 'rejected', reason: Error },
    // }
    // Note that for rejectedPromise, reason.message == 'rejection',
    // and for anotherRejectedPromise, reason.message == 'more rejection'.
  });
  ```

  An important note: `RSVP.hashSettled` is intended for plain JavaScript objects that
  are just a set of keys and values. `RSVP.hashSettled` will NOT preserve prototype
  chains.

  Example:

  ```javascript
  function MyConstructor(){
    this.example = RSVP.Promise.resolve('Example');
  }

  MyConstructor.prototype = {
    protoProperty: RSVP.Promise.resolve('Proto Property')
  };

  var myObject = new MyConstructor();

  RSVP.hashSettled(myObject).then(function(hash){
    // protoProperty will not be present, instead you will just have an
    // object that looks like:
    // {
    //   example: { state: 'fulfilled', value: 'Example' }
    // }
    //
    // hash.hasOwnProperty('protoProperty'); // false
    // 'undefined' === typeof hash.protoProperty
  });
  ```

  @method hashSettled
  @for RSVP
  @param {Object} promises
  @param {String} label optional string that describes the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled when when all properties of `promises`
  have been settled.
  @static
*/
exports['default'] = function hashSettled(object, label) {
    return new HashSettled(Promise, object, label).promise;
};
},{"./enumerator":14,"./promise":23,"./promise-hash":22,"./utils":33}],18:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
var PromiseHash = require('./promise-hash')['default'];
var ABORT_ON_REJECTION = require('./enumerator').ABORT_ON_REJECTION;
/**
  `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array
  for its `promises` argument.

  Returns a promise that is fulfilled when all the given promises have been
  fulfilled, or rejected if any of them become rejected. The returned promise
  is fulfilled with a hash that has the same key names as the `promises` object
  argument. If any of the values in the object are not promises, they will
  simply be copied over to the fulfilled object.

  Example:

  ```javascript
  var promises = {
    myPromise: RSVP.resolve(1),
    yourPromise: RSVP.resolve(2),
    theirPromise: RSVP.resolve(3),
    notAPromise: 4
  };

  RSVP.hash(promises).then(function(hash){
    // hash here is an object that looks like:
    // {
    //   myPromise: 1,
    //   yourPromise: 2,
    //   theirPromise: 3,
    //   notAPromise: 4
    // }
  });
  ````

  If any of the `promises` given to `RSVP.hash` are rejected, the first promise
  that is rejected will be given as the reason to the rejection handler.

  Example:

  ```javascript
  var promises = {
    myPromise: RSVP.resolve(1),
    rejectedPromise: RSVP.reject(new Error("rejectedPromise")),
    anotherRejectedPromise: RSVP.reject(new Error("anotherRejectedPromise")),
  };

  RSVP.hash(promises).then(function(hash){
    // Code here never runs because there are rejected promises!
  }, function(reason) {
    // reason.message === "rejectedPromise"
  });
  ```

  An important note: `RSVP.hash` is intended for plain JavaScript objects that
  are just a set of keys and values. `RSVP.hash` will NOT preserve prototype
  chains.

  Example:

  ```javascript
  function MyConstructor(){
    this.example = RSVP.resolve("Example");
  }

  MyConstructor.prototype = {
    protoProperty: RSVP.resolve("Proto Property")
  };

  var myObject = new MyConstructor();

  RSVP.hash(myObject).then(function(hash){
    // protoProperty will not be present, instead you will just have an
    // object that looks like:
    // {
    //   example: "Example"
    // }
    //
    // hash.hasOwnProperty('protoProperty'); // false
    // 'undefined' === typeof hash.protoProperty
  });
  ```

  @method hash
  @static
  @for RSVP
  @param {Object} promises
  @param {String} label optional string that describes the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled when all properties of `promises`
  have been fulfilled, or rejected if any of them become rejected.
*/
exports['default'] = function hash(object, label) {
    return new PromiseHash(Promise, object, label).promise;
};
},{"./enumerator":14,"./promise":23,"./promise-hash":22}],19:[function(require,module,exports){
'use strict';
var config = require('./config').config;
var now = require('./utils').now;
var queue = [];
exports['default'] = function instrument(eventName, promise, child) {
    if (1 === queue.push({
            name: eventName,
            payload: {
                guid: promise._guidKey + promise._id,
                eventName: eventName,
                detail: promise._result,
                childGuid: child && promise._guidKey + child._id,
                label: promise._label,
                timeStamp: now(),
                stack: new Error(promise._label).stack
            }
        })) {
        setTimeout(function () {
            var entry;
            for (var i = 0; i < queue.length; i++) {
                entry = queue[i];
                config.trigger(entry.name, entry.payload);
            }
            queue.length = 0;
        }, 50);
    }
};
},{"./config":12,"./utils":33}],20:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
var isArray = require('./utils').isArray;
var isFunction = require('./utils').isFunction;
/**
 `RSVP.map` is similar to JavaScript's native `map` method, except that it
  waits for all promises to become fulfilled before running the `mapFn` on
  each item in given to `promises`. `RSVP.map` returns a promise that will
  become fulfilled with the result of running `mapFn` on the values the promises
  become fulfilled with.

  For example:

  ```javascript

  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.resolve(2);
  var promise3 = RSVP.resolve(3);
  var promises = [ promise1, promise2, promise3 ];

  var mapFn = function(item){
    return item + 1;
  };

  RSVP.map(promises, mapFn).then(function(result){
    // result is [ 2, 3, 4 ]
  });
  ```

  If any of the `promises` given to `RSVP.map` are rejected, the first promise
  that is rejected will be given as an argument to the returned promise's
  rejection handler. For example:

  ```javascript
  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.reject(new Error("2"));
  var promise3 = RSVP.reject(new Error("3"));
  var promises = [ promise1, promise2, promise3 ];

  var mapFn = function(item){
    return item + 1;
  };

  RSVP.map(promises, mapFn).then(function(array){
    // Code here never runs because there are rejected promises!
  }, function(reason) {
    // reason.message === "2"
  });
  ```

  `RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
  say you want to get all comments from a set of blog posts, but you need
  the blog posts first because they contain a url to those comments.

  ```javscript

  var mapFn = function(blogPost){
    // getComments does some ajax and returns an RSVP.Promise that is fulfilled
    // with some comments data
    return getComments(blogPost.comments_url);
  };

  // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
  // with some blog post data
  RSVP.map(getBlogPosts(), mapFn).then(function(comments){
    // comments is the result of asking the server for the comments
    // of all blog posts returned from getBlogPosts()
  });
  ```

  @method map
  @static
  @for RSVP
  @param {Array} promises
  @param {Function} mapFn function to be called on each fulfilled promise.
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled with the result of calling
  `mapFn` on each fulfilled promise or value when they become fulfilled.
   The promise will be rejected if any of the given `promises` become rejected.
  @static
*/
exports['default'] = function map(promises, mapFn, label) {
    return Promise.all(promises, label).then(function (values) {
        if (!isFunction(mapFn)) {
            throw new TypeError('You must pass a function as map\'s second argument.');
        }
        var length = values.length;
        var results = new Array(length);
        for (var i = 0; i < length; i++) {
            results[i] = mapFn(values[i]);
        }
        return Promise.all(results, label);
    });
};
},{"./promise":23,"./utils":33}],21:[function(require,module,exports){
'use strict';
/* global  arraySlice */
var Promise = require('./promise')['default'];
var isArray = require('./utils').isArray;
/**
  `RSVP.denodeify` takes a "node-style" function and returns a function that
  will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
  browser when you'd prefer to use promises over using callbacks. For example,
  `denodeify` transforms the following:

  ```javascript
  var fs = require('fs');

  fs.readFile('myfile.txt', function(err, data){
    if (err) return handleError(err);
    handleData(data);
  });
  ```

  into:

  ```javascript
  var fs = require('fs');
  var readFile = RSVP.denodeify(fs.readFile);

  readFile('myfile.txt').then(handleData, handleError);
  ```

  If the node function has multiple success parameters, then `denodeify`
  just returns the first one:

  ```javascript
  var request = RSVP.denodeify(require('request'));

  request('http://example.com').then(function(res) {
    // ...
  });
  ```

  However, if you need all success parameters, setting `denodeify`'s
  second parameter to `true` causes it to return all success parameters
  as an array:

  ```javascript
  var request = RSVP.denodeify(require('request'), true);

  request('http://example.com').then(function(result) {
    // result[0] -> res
    // result[1] -> body
  });
  ```

  Or if you pass it an array with names it returns the parameters as a hash:

  ```javascript
  var request = RSVP.denodeify(require('request'), ['res', 'body']);

  request('http://example.com').then(function(result) {
    // result.res
    // result.body
  });
  ```

  Sometimes you need to retain the `this`:

  ```javascript
  var app = require('express')();
  var render = RSVP.denodeify(app.render.bind(app));
  ```

  The denodified function inherits from the original function. It works in all
  environments, except IE 10 and below. Consequently all properties of the original
  function are available to you. However, any properties you change on the
  denodeified function won't be changed on the original function. Example:

  ```javascript
  var request = RSVP.denodeify(require('request')),
      cookieJar = request.jar(); // <- Inheritance is used here

  request('http://example.com', {jar: cookieJar}).then(function(res) {
    // cookieJar.cookies holds now the cookies returned by example.com
  });
  ```

  Using `denodeify` makes it easier to compose asynchronous operations instead
  of using callbacks. For example, instead of:

  ```javascript
  var fs = require('fs');

  fs.readFile('myfile.txt', function(err, data){
    if (err) { ... } // Handle error
    fs.writeFile('myfile2.txt', data, function(err){
      if (err) { ... } // Handle error
      console.log('done')
    });
  });
  ```

  you can chain the operations together using `then` from the returned promise:

  ```javascript
  var fs = require('fs');
  var readFile = RSVP.denodeify(fs.readFile);
  var writeFile = RSVP.denodeify(fs.writeFile);

  readFile('myfile.txt').then(function(data){
    return writeFile('myfile2.txt', data);
  }).then(function(){
    console.log('done')
  }).catch(function(error){
    // Handle error
  });
  ```

  @method denodeify
  @static
  @for RSVP
  @param {Function} nodeFunc a "node-style" function that takes a callback as
  its last argument. The callback expects an error to be passed as its first
  argument (if an error occurred, otherwise null), and the value from the
  operation as its second argument ("function(err, value){ }").
  @param {Boolean|Array} argumentNames An optional paramter that if set
  to `true` causes the promise to fulfill with the callback's success arguments
  as an array. This is useful if the node function has multiple success
  paramters. If you set this paramter to an array with names, the promise will
  fulfill with a hash with these names as keys and the success parameters as
  values.
  @return {Function} a function that wraps `nodeFunc` to return an
  `RSVP.Promise`
  @static
*/
exports['default'] = function denodeify(nodeFunc, argumentNames) {
    var asArray = argumentNames === true;
    var asHash = isArray(argumentNames);
    function denodeifiedFunction() {
        var length = arguments.length;
        var nodeArgs = new Array(length);
        for (var i = 0; i < length; i++) {
            nodeArgs[i] = arguments[i];
        }
        var thisArg;
        if (!asArray && !asHash && argumentNames) {
            if (typeof console === 'object') {
                console.warn('Deprecation: RSVP.denodeify() doesn\'t allow setting the ' + '"this" binding anymore. Use yourFunction.bind(yourThis) instead.');
            }
            thisArg = argumentNames;
        } else {
            thisArg = this;
        }
        return Promise.all(nodeArgs).then(function (nodeArgs$2) {
            return new Promise(resolver);
            // sweet.js has a bug, this resolver can't be defined in the constructor
            // or the arraySlice macro doesn't work
            function resolver(resolve, reject) {
                function callback() {
                    var length$2 = arguments.length;
                    var args = new Array(length$2);
                    for (var i$2 = 0; i$2 < length$2; i$2++) {
                        args[i$2] = arguments[i$2];
                    }
                    var error = args[0];
                    var value = args[1];
                    if (error) {
                        reject(error);
                    } else if (asArray) {
                        resolve(args.slice(1));
                    } else if (asHash) {
                        var obj = {};
                        var successArguments = args.slice(1);
                        var name;
                        var i$3;
                        for (i$3 = 0; i$3 < argumentNames.length; i$3++) {
                            name = argumentNames[i$3];
                            obj[name] = successArguments[i$3];
                        }
                        resolve(obj);
                    } else {
                        resolve(value);
                    }
                }
                nodeArgs$2.push(callback);
                nodeFunc.apply(thisArg, nodeArgs$2);
            }
        });
    }
    denodeifiedFunction.__proto__ = nodeFunc;
    return denodeifiedFunction;
};
},{"./promise":23,"./utils":33}],22:[function(require,module,exports){
'use strict';
var Enumerator = require('./enumerator')['default'];
var PENDING = require('./-internal').PENDING;
var FULFILLED = require('./-internal').FULFILLED;
var o_create = require('./utils').o_create;
function PromiseHash(Constructor, object, label) {
    this._superConstructor(Constructor, object, true, label);
}
exports['default'] = PromiseHash;
PromiseHash.prototype = o_create(Enumerator.prototype);
PromiseHash.prototype._superConstructor = Enumerator;
PromiseHash.prototype._init = function () {
    this._result = {};
};
PromiseHash.prototype._validateInput = function (input) {
    return input && typeof input === 'object';
};
PromiseHash.prototype._validationError = function () {
    return new Error('Promise.hash must be called with an object');
};
PromiseHash.prototype._enumerate = function () {
    var promise = this.promise;
    var input = this._input;
    var results = [];
    for (var key in input) {
        if (promise._state === PENDING && input.hasOwnProperty(key)) {
            results.push({
                position: key,
                entry: input[key]
            });
        }
    }
    var length = results.length;
    this._remaining = length;
    var result;
    for (var i = 0; promise._state === PENDING && i < length; i++) {
        result = results[i];
        this._eachEntry(result.entry, result.position);
    }
};
},{"./-internal":8,"./enumerator":14,"./utils":33}],23:[function(require,module,exports){
'use strict';
var config = require('./config').config;
var EventTarget = require('./events')['default'];
var instrument = require('./instrument')['default'];
var objectOrFunction = require('./utils').objectOrFunction;
var isFunction = require('./utils').isFunction;
var now = require('./utils').now;
var noop = require('./-internal').noop;
var resolve = require('./-internal').resolve;
var reject = require('./-internal').reject;
var fulfill = require('./-internal').fulfill;
var subscribe = require('./-internal').subscribe;
var initializePromise = require('./-internal').initializePromise;
var invokeCallback = require('./-internal').invokeCallback;
var FULFILLED = require('./-internal').FULFILLED;
var REJECTED = require('./-internal').REJECTED;
var cast = require('./promise/cast')['default'];
var all = require('./promise/all')['default'];
var race = require('./promise/race')['default'];
var Resolve = require('./promise/resolve')['default'];
var Reject = require('./promise/reject')['default'];
var guidKey = 'rsvp_' + now() + '-';
var counter = 0;
function needsResolver() {
    throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
}
function needsNew() {
    throw new TypeError('Failed to construct \'Promise\': Please use the \'new\' operator, this object constructor cannot be called as a function.');
}
exports['default'] = Promise;
/**
  Promise objects represent the eventual result of an asynchronous operation. The
  primary way of interacting with a promise is through its `then` method, which
  registers callbacks to receive either a promise’s eventual value or the reason
  why the promise cannot be fulfilled.

  Terminology
  -----------

  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
  - `thenable` is an object or function that defines a `then` method.
  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
  - `exception` is a value that is thrown using the throw statement.
  - `reason` is a value that indicates why a promise was rejected.
  - `settled` the final resting state of a promise, fulfilled or rejected.

  A promise can be in one of three states: pending, fulfilled, or rejected.

  Promises that are fulfilled have a fulfillment value and are in the fulfilled
  state.  Promises that are rejected have a rejection reason and are in the
  rejected state.  A fulfillment value is never a thenable.

  Promises can also be said to *resolve* a value.  If this value is also a
  promise, then the original promise's settled state will match the value's
  settled state.  So a promise that *resolves* a promise that rejects will
  itself reject, and a promise that *resolves* a promise that fulfills will
  itself fulfill.


  Basic Usage:
  ------------

  ```js
  var promise = new Promise(function(resolve, reject) {
    // on success
    resolve(value);

    // on failure
    reject(reason);
  });

  promise.then(function(value) {
    // on fulfillment
  }, function(reason) {
    // on rejection
  });
  ```

  Advanced Usage:
  ---------------

  Promises shine when abstracting away asynchronous interactions such as
  `XMLHttpRequest`s.

  ```js
  function getJSON(url) {
    return new Promise(function(resolve, reject){
      var xhr = new XMLHttpRequest();

      xhr.open('GET', url);
      xhr.onreadystatechange = handler;
      xhr.responseType = 'json';
      xhr.setRequestHeader('Accept', 'application/json');
      xhr.send();

      function handler() {
        if (this.readyState === this.DONE) {
          if (this.status === 200) {
            resolve(this.response);
          } else {
            reject(new Error("getJSON: `" + url + "` failed with status: [" + this.status + "]"));
          }
        }
      };
    });
  }

  getJSON('/posts.json').then(function(json) {
    // on fulfillment
  }, function(reason) {
    // on rejection
  });
  ```

  Unlike callbacks, promises are great composable primitives.

  ```js
  Promise.all([
    getJSON('/posts'),
    getJSON('/comments')
  ]).then(function(values){
    values[0] // => postsJSON
    values[1] // => commentsJSON

    return values;
  });
  ```

  @class RSVP.Promise
  @param {function} resolver
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @constructor
*/
function Promise(resolver, label) {
    this._id = counter++;
    this._label = label;
    this._subscribers = [];
    if (config.instrument) {
        instrument('created', this);
    }
    if (noop !== resolver) {
        if (!isFunction(resolver)) {
            needsResolver();
        }
        if (!(this instanceof Promise)) {
            needsNew();
        }
        initializePromise(this, resolver);
    }
}
Promise.cast = cast;
Promise.all = all;
Promise.race = race;
Promise.resolve = Resolve;
Promise.reject = Reject;
Promise.prototype = {
    constructor: Promise,
    _id: undefined,
    _guidKey: guidKey,
    _label: undefined,
    _state: undefined,
    _result: undefined,
    _subscribers: undefined,
    _onerror: function (reason) {
        config.trigger('error', reason);
    },
    then: function (onFulfillment, onRejection, label) {
        var parent = this;
        var state = parent._state;
        if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
            if (config.instrument) {
                instrument('chained', this, this);
            }
            return this;
        }
        parent._onerror = null;
        var child = new this.constructor(noop, label);
        var result = parent._result;
        if (config.instrument) {
            instrument('chained', parent, child);
        }
        if (state) {
            var callback = arguments[state - 1];
            config.async(function () {
                invokeCallback(state, child, callback, result);
            });
        } else {
            subscribe(parent, child, onFulfillment, onRejection);
        }
        return child;
    },
    'catch': function (onRejection, label) {
        return this.then(null, onRejection, label);
    },
    'finally': function (callback, label) {
        var constructor = this.constructor;
        return this.then(function (value) {
            return constructor.resolve(callback()).then(function () {
                return value;
            });
        }, function (reason) {
            return constructor.resolve(callback()).then(function () {
                throw reason;
            });
        }, label);
    }
};
},{"./-internal":8,"./config":12,"./events":15,"./instrument":19,"./promise/all":24,"./promise/cast":25,"./promise/race":26,"./promise/reject":27,"./promise/resolve":28,"./utils":33}],24:[function(require,module,exports){
'use strict';
var Enumerator = require('../enumerator')['default'];
/**
  `RSVP.Promise.all` accepts an array of promises, and returns a new promise which
  is fulfilled with an array of fulfillment values for the passed promises, or
  rejected with the reason of the first passed promise to be rejected. It casts all
  elements of the passed iterable to promises as it runs this algorithm.

  Example:

  ```javascript
  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.resolve(2);
  var promise3 = RSVP.resolve(3);
  var promises = [ promise1, promise2, promise3 ];

  RSVP.Promise.all(promises).then(function(array){
    // The array here would be [ 1, 2, 3 ];
  });
  ```

  If any of the `promises` given to `RSVP.all` are rejected, the first promise
  that is rejected will be given as an argument to the returned promises's
  rejection handler. For example:

  Example:

  ```javascript
  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.reject(new Error("2"));
  var promise3 = RSVP.reject(new Error("3"));
  var promises = [ promise1, promise2, promise3 ];

  RSVP.Promise.all(promises).then(function(array){
    // Code here never runs because there are rejected promises!
  }, function(error) {
    // error.message === "2"
  });
  ```

  @method all
  @static
  @param {Array} entries array of promises
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled when all `promises` have been
  fulfilled, or rejected if any of them become rejected.
  @static
*/
exports['default'] = function all(entries, label) {
    return new Enumerator(this, entries, true, label).promise;
};
},{"../enumerator":14}],25:[function(require,module,exports){
'use strict';
var resolve = require('./resolve')['default'];
/**
  @deprecated

  `RSVP.Promise.cast` coerces its argument to a promise, or returns the
  argument if it is already a promise which shares a constructor with the caster.

  Example:

  ```javascript
  var promise = RSVP.Promise.resolve(1);
  var casted = RSVP.Promise.cast(promise);

  console.log(promise === casted); // true
  ```

  In the case of a promise whose constructor does not match, it is assimilated.
  The resulting promise will fulfill or reject based on the outcome of the
  promise being casted.

  Example:

  ```javascript
  var thennable = $.getJSON('/api/foo');
  var casted = RSVP.Promise.cast(thennable);

  console.log(thennable === casted); // false
  console.log(casted instanceof RSVP.Promise) // true

  casted.then(function(data) {
    // data is the value getJSON fulfills with
  });
  ```

  In the case of a non-promise, a promise which will fulfill with that value is
  returned.

  Example:

  ```javascript
  var value = 1; // could be a number, boolean, string, undefined...
  var casted = RSVP.Promise.cast(value);

  console.log(value === casted); // false
  console.log(casted instanceof RSVP.Promise) // true

  casted.then(function(val) {
    val === value // => true
  });
  ```

  `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the
  following ways:

  * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you
  have something that could either be a promise or a value. RSVP.resolve
  will have the same effect but will create a new promise wrapper if the
  argument is a promise.
  * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to
  promises of the exact class specified, so that the resulting object's `then` is
  ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise).

  @method cast
  @static
  @param {Object} object to be casted
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @return {Promise} promise
*/
exports['default'] = resolve;
},{"./resolve":28}],26:[function(require,module,exports){
'use strict';
var isArray = require('../utils').isArray;
var isFunction = require('../utils').isFunction;
var isMaybeThenable = require('../utils').isMaybeThenable;
var noop = require('../-internal').noop;
var resolve = require('../-internal').resolve;
var reject = require('../-internal').reject;
var subscribe = require('../-internal').subscribe;
var PENDING = require('../-internal').PENDING;
/**
  `RSVP.Promise.race` returns a new promise which is settled in the same way as the
  first passed promise to settle.

  Example:

  ```javascript
  var promise1 = new RSVP.Promise(function(resolve, reject){
    setTimeout(function(){
      resolve("promise 1");
    }, 200);
  });

  var promise2 = new RSVP.Promise(function(resolve, reject){
    setTimeout(function(){
      resolve("promise 2");
    }, 100);
  });

  RSVP.Promise.race([promise1, promise2]).then(function(result){
    // result === "promise 2" because it was resolved before promise1
    // was resolved.
  });
  ```

  `RSVP.Promise.race` is deterministic in that only the state of the first
  settled promise matters. For example, even if other promises given to the
  `promises` array argument are resolved, but the first settled promise has
  become rejected before the other promises became fulfilled, the returned
  promise will become rejected:

  ```javascript
  var promise1 = new RSVP.Promise(function(resolve, reject){
    setTimeout(function(){
      resolve("promise 1");
    }, 200);
  });

  var promise2 = new RSVP.Promise(function(resolve, reject){
    setTimeout(function(){
      reject(new Error("promise 2"));
    }, 100);
  });

  RSVP.Promise.race([promise1, promise2]).then(function(result){
    // Code here never runs
  }, function(reason){
    // reason.message === "promise 2" because promise 2 became rejected before
    // promise 1 became fulfilled
  });
  ```

  An example real-world use case is implementing timeouts:

  ```javascript
  RSVP.Promise.race([ajax('foo.json'), timeout(5000)])
  ```

  @method race
  @static
  @param {Array} promises array of promises to observe
  @param {String} label optional string for describing the promise returned.
  Useful for tooling.
  @return {Promise} a promise which settles in the same way as the first passed
  promise to settle.
*/
exports['default'] = function race(entries, label) {
    /*jshint validthis:true */
    var Constructor = this, entry;
    var promise = new Constructor(noop, label);
    if (!isArray(entries)) {
        reject(promise, new TypeError('You must pass an array to race.'));
        return promise;
    }
    var length = entries.length;
    function onFulfillment(value) {
        resolve(promise, value);
    }
    function onRejection(reason) {
        reject(promise, reason);
    }
    for (var i = 0; promise._state === PENDING && i < length; i++) {
        subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
    }
    return promise;
};
},{"../-internal":8,"../utils":33}],27:[function(require,module,exports){
'use strict';
var noop = require('../-internal').noop;
var _reject = require('../-internal').reject;
/**
  `RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
  It is shorthand for the following:

  ```javascript
  var promise = new RSVP.Promise(function(resolve, reject){
    reject(new Error('WHOOPS'));
  });

  promise.then(function(value){
    // Code here doesn't run because the promise is rejected!
  }, function(reason){
    // reason.message === 'WHOOPS'
  });
  ```

  Instead of writing the above, your code now simply becomes the following:

  ```javascript
  var promise = RSVP.Promise.reject(new Error('WHOOPS'));

  promise.then(function(value){
    // Code here doesn't run because the promise is rejected!
  }, function(reason){
    // reason.message === 'WHOOPS'
  });
  ```

  @method reject
  @static
  @param {Any} reason value that the returned promise will be rejected with.
  @param {String} label optional string for identifying the returned promise.
  Useful for tooling.
  @return {Promise} a promise rejected with the given `reason`.
*/
exports['default'] = function reject(reason, label) {
    /*jshint validthis:true */
    var Constructor = this;
    var promise = new Constructor(noop, label);
    _reject(promise, reason);
    return promise;
};
},{"../-internal":8}],28:[function(require,module,exports){
'use strict';
var noop = require('../-internal').noop;
var _resolve = require('../-internal').resolve;
/**
  `RSVP.Promise.resolve` returns a promise that will become resolved with the
  passed `value`. It is shorthand for the following:

  ```javascript
  var promise = new RSVP.Promise(function(resolve, reject){
    resolve(1);
  });

  promise.then(function(value){
    // value === 1
  });
  ```

  Instead of writing the above, your code now simply becomes the following:

  ```javascript
  var promise = RSVP.Promise.resolve(1);

  promise.then(function(value){
    // value === 1
  });
  ```

  @method resolve
  @static
  @param {Any} value value that the returned promise will be resolved with
  @param {String} label optional string for identifying the returned promise.
  Useful for tooling.
  @return {Promise} a promise that will become fulfilled with the given
  `value`
*/
exports['default'] = function resolve(object, label) {
    /*jshint validthis:true */
    var Constructor = this;
    if (object && typeof object === 'object' && object.constructor === Constructor) {
        return object;
    }
    var promise = new Constructor(noop, label);
    _resolve(promise, object);
    return promise;
};
},{"../-internal":8}],29:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  This is a convenient alias for `RSVP.Promise.race`.

  @method race
  @static
  @for RSVP
  @param {Array} array Array of promises.
  @param {String} label An optional label. This is useful
  for tooling.
 */
exports['default'] = function race(array, label) {
    return Promise.race(array, label);
};
},{"./promise":23}],30:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  This is a convenient alias for `RSVP.Promise.reject`.

  @method reject
  @static
  @for RSVP
  @param {Any} reason value that the returned promise will be rejected with.
  @param {String} label optional string for identifying the returned promise.
  Useful for tooling.
  @return {Promise} a promise rejected with the given `reason`.
*/
exports['default'] = function reject(reason, label) {
    return Promise.reject(reason, label);
};
},{"./promise":23}],31:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  This is a convenient alias for `RSVP.Promise.resolve`.

  @method resolve
  @static
  @for RSVP
  @param {Any} value value that the returned promise will be resolved with
  @param {String} label optional string for identifying the returned promise.
  Useful for tooling.
  @return {Promise} a promise that will become fulfilled with the given
  `value`
*/
exports['default'] = function resolve(value, label) {
    return Promise.resolve(value, label);
};
},{"./promise":23}],32:[function(require,module,exports){
'use strict';
/**
  `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event
  loop in order to aid debugging.

  Promises A+ specifies that any exceptions that occur with a promise must be
  caught by the promises implementation and bubbled to the last handler. For
  this reason, it is recommended that you always specify a second rejection
  handler function to `then`. However, `RSVP.rethrow` will throw the exception
  outside of the promise, so it bubbles up to your console if in the browser,
  or domain/cause uncaught exception in Node. `rethrow` will also throw the
  error again so the error can be handled by the promise per the spec.

  ```javascript
  function throws(){
    throw new Error('Whoops!');
  }

  var promise = new RSVP.Promise(function(resolve, reject){
    throws();
  });

  promise.catch(RSVP.rethrow).then(function(){
    // Code here doesn't run because the promise became rejected due to an
    // error!
  }, function (err){
    // handle the error here
  });
  ```

  The 'Whoops' error will be thrown on the next turn of the event loop
  and you can watch for it in your console. You can also handle it using a
  rejection handler given to `.then` or `.catch` on the returned promise.

  @method rethrow
  @static
  @for RSVP
  @param {Error} reason reason the promise became rejected.
  @throws Error
  @static
*/
exports['default'] = function rethrow(reason) {
    setTimeout(function () {
        throw reason;
    });
    throw reason;
};
},{}],33:[function(require,module,exports){
'use strict';
function objectOrFunction(x) {
    return typeof x === 'function' || typeof x === 'object' && x !== null;
}
exports.objectOrFunction = objectOrFunction;
function isFunction(x) {
    return typeof x === 'function';
}
exports.isFunction = isFunction;
function isMaybeThenable(x) {
    return typeof x === 'object' && x !== null;
}
exports.isMaybeThenable = isMaybeThenable;
var _isArray;
if (!Array.isArray) {
    _isArray = function (x) {
        return Object.prototype.toString.call(x) === '[object Array]';
    };
} else {
    _isArray = Array.isArray;
}
var isArray = _isArray;
exports.isArray = isArray;
// Date.now is not available in browsers < IE9
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
var now = Date.now || function () {
        return new Date().getTime();
    };
exports.now = now;
var o_create = Object.create || function (object) {
        var o = function () {
        };
        o.prototype = object;
        return o;
    };
exports.o_create = o_create;
},{}]},{},[1])
(1)
});
;
define('models/dual-sensor-connector/units-definition',[],function() {
  return {
    units: {
      time: {
        name: "second",
        pluralName: "seconds",
        symbol: "s"
      }
    }
  };
});

define('models/sensor-common/i18n-sensor-definitions-connector',[],function() {
  // Information about sensor types, indexed by the units reported by the LabQuest2.
  return function(i18n) {
    return {
      "lux": {
        "sensorName": i18n.t("sensor.names.light"),
        "measurementName": i18n.t("sensor.measurements.light_level"),
        "measurementType": "light level",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 2000.0
      },
      "m": {
        "sensorName": i18n.t("sensor.names.motion"),
        "measurementName": i18n.t("sensor.measurements.position"),
        "measurementType": "position",
        "tareable": true,
        "minReading": -2.0,
        "maxReading": 2.0
      },
      "m/s²": {
        "sensorName": i18n.t("sensor.names.accelerometer"),
        "measurementName": i18n.t("sensor.measurements.acceleration"),
        "measurementType": "acceleration",
        "tareable": true,
        "minReading": -50.0,
        "maxReading": 50.0
      },
      "m/s^2": {
        "sensorName": i18n.t("sensor.names.accelerometer"),
        "measurementName": i18n.t("sensor.measurements.acceleration"),
        "measurementType": "acceleration",
        "tareable": true,
        "minReading": -50.0,
        "maxReading": 50.0
      },
      "g": {
        "sensorName": i18n.t("sensor.names.accelerometer"),
        "measurementName": i18n.t("sensor.measurements.acceleration"),
        "measurementType": "acceleration",
        "tareable": true,
        "minReading": -5.0,
        "maxReading": 5.0
      },
      "N/kg": {
        "sensorName": i18n.t("sensor.names.accelerometer"),
        "measurementName": i18n.t("sensor.measurements.acceleration"),
        "measurementType": "acceleration",
        "tareable": true,
        "minReading": -25.0,
        "maxReading": 25.0
      },
      "mg/L": {
        "sensorName": i18n.t("sensor.names.dissolved_oxygen"),
        "measurementName": i18n.t("sensor.measurements.dissolved_oxygen"),
        "measurementType": "do",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 12.0
      },
      "kPa": {
        "sensorName": i18n.t("sensor.names.pressure"),
        "measurementName": i18n.t("sensor.measurements.pressure"),
        "measurementType": "pressure",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 220.0
      },
      "mm Hg": {
        "sensorName": i18n.t("sensor.names.pressure"),
        "measurementName": i18n.t("sensor.measurements.pressure"),
        "measurementType": "pressure",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 2000.0
      },
      "in Hg": {
        "sensorName": i18n.t("sensor.names.pressure"),
        "measurementName": i18n.t("sensor.measurements.pressure"),
        "measurementType": "pressure",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 80.0
      },
      "mbar": {
        "sensorName": i18n.t("sensor.names.pressure"),
        "measurementName": i18n.t("sensor.measurements.pressure"),
        "measurementType": "pressure",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 2200.0
      },
      "psi": {
        "sensorName": i18n.t("sensor.names.pressure"),
        "measurementName": i18n.t("sensor.measurements.pressure"),
        "measurementType": "pressure",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 40.0
      },
      "atm": {
        "sensorName": i18n.t("sensor.names.pressure"),
        "measurementName": i18n.t("sensor.measurements.pressure"),
        "measurementType": "pressure",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 2.5
      },
      "torr": {
        "sensorName": i18n.t("sensor.names.pressure"),
        "measurementName": i18n.t("sensor.measurements.pressure"),
        "measurementType": "pressure",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 2000.0
      },
      "nC": {
        "sensorName": i18n.t("sensor.names.charge_sensor"),
        "measurementName": i18n.t("sensor.measurements.charge"),
        "measurementType": "charge",
        "tareable": false,
        "minReading": -20.0,
        "maxReading": 20.0
      },
      "V": {
        "sensorName": i18n.t("sensor.names.voltage"),
        "measurementName": i18n.t("sensor.measurements.potential"),
        "measurementType": "potential",
        "tareable": true,
        "minReading": -30.0,
        "maxReading": 30.0
      },
      "pH": {
        "sensorName": i18n.t("sensor.names.pH"),
        "measurementName": i18n.t("sensor.measurements.pH"),
        "measurementType": "ph",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 14.0
      },
      "ppm": {
        "sensorName": i18n.t("sensor.names.CO2_gas"),
        "measurementName": i18n.t("sensor.measurements.CO2"),
        "measurementType": "co2",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 5000.0
      },
      "ppt": {
        "sensorName": i18n.t("sensor.names.CO2_gas"),
        "measurementName": i18n.t("sensor.measurements.CO2"),
        "measurementType": "co2",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 5.0
      },
      "%": {
        "sensorName": i18n.t("sensor.names.CO2_gas"),
        "measurementName": i18n.t("sensor.measurements.CO2"),
        "measurementType": "co2",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 0.5
      },
      "%T": {
        "sensorName": i18n.t("sensor.names.colorimeter"),
        "measurementName": i18n.t("sensor.measurements.transmittance"),
        "measurementType": "transmittance",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 100.0
      },
      "µS/cm": {
        "sensorName": i18n.t("sensor.names.conductivity"),
        "measurementName": i18n.t("sensor.measurements.conductivity"),
        "measurementType": "conductivity",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 2000.0
      },
      "dS/m": {
        "sensorName": i18n.t("sensor.names.conductivity"),
        "measurementName": i18n.t("sensor.measurements.conductivity"),
        "measurementType": "conductivity",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 2.0
      },
      "A": {
        "sensorName": i18n.t("sensor.names.current"),
        "measurementName": i18n.t("sensor.measurements.current"),
        "measurementType": "current",
        "tareable": true,
        "minReading": -1.2,
        "maxReading": 1.2
      },
      "mA": {
        "sensorName": i18n.t("sensor.names.current"),
        "measurementName": i18n.t("sensor.measurements.current"),
        "measurementType": "current",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 500.0
      },
      "°C": {
        "sensorName": i18n.t("sensor.names.temperature"),
        "measurementName": i18n.t("sensor.measurements.temperature"),
        "measurementType": "temperature",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 40.0
      },
      "degC": {
        "sensorName": i18n.t("sensor.names.temperature"),
        "measurementName": i18n.t("sensor.measurements.temperature"),
        "measurementType": "temperature",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 40.0
      },
      "°F": {
        "sensorName": i18n.t("sensor.names.temperature"),
        "measurementName": i18n.t("sensor.measurements.temperature"),
        "measurementType": "temperature",
        "tareable": false,
        "minReading": 30.0,
        "maxReading": 100.0
      },
      "degF": {
        "sensorName": i18n.t("sensor.names.temperature"),
        "measurementName": i18n.t("sensor.measurements.temperature"),
        "measurementType": "temperature",
        "tareable": false,
        "minReading": 30.0,
        "maxReading": 100.0
      },
      "K": {
        "sensorName": i18n.t("sensor.names.temperature"),
        "measurementName": i18n.t("sensor.measurements.temperature"),
        "measurementType": "temperature",
        "tareable": false,
        "minReading": 250.0,
        "maxReading": 400.0
      },
      "N": {
        "sensorName": i18n.t("sensor.names.force"),
        "measurementName": i18n.t("sensor.measurements.force"),
        "measurementType": "force",
        "tareable": true,
        "minReading": -50.0,
        "maxReading": 50.0
      },
      "lb": {
        "sensorName": i18n.t("sensor.names.force"),
        "measurementName": i18n.t("sensor.measurements.force"),
        "measurementType": "force",
        "tareable": true,
        "minReading": -12.5,
        "maxReading": 12.5
      },
      "mV": {
        "sensorName": null,
        "measurementName": i18n.t("sensor.measurements.potential"),
        "measurementType": "potential",
        "tareable": true,
        "minReading": -500.0,
        "maxReading": 1100.0
      },
      "m/s": {
        "sensorName": i18n.t("sensor.names.motion"),
        "measurementName": i18n.t("sensor.measurements.velocity"),
        "measurementType": "velocity",
        "tareable": true,
        "minReading": -5.0,
        "maxReading": 5.0
      },
      "ft/s": {
        "sensorName": i18n.t("sensor.names.anemometer"),
        "measurementName": i18n.t("sensor.measurements.speed"),
        "measurementType": "speed",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 100.0
      },
      "ft": {
        "sensorName": i18n.t("sensor.names.motion"),
        "measurementName": i18n.t("sensor.measurements.position"),
        "measurementType": "position",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 6.0
      },
      "kg": {
        "sensorName": i18n.t("sensor.names.hand_dynamometer"),
        "measurementName": i18n.t("sensor.measurements.force"),
        "measurementType": "force",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 50.0
      },
      "v": {
        "sensorName": i18n.t("sensor.names.heart_rate"),
        "measurementName": i18n.t("sensor.measurements.signal"),
        "measurementType": "signal",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 3.0
      },
      "mT": {
        "sensorName": i18n.t("sensor.names.magnetic_field"),
        "measurementName": i18n.t("sensor.measurements.magnetic_field"),
        "measurementType": "magnetic field",
        "tareable": true,
        "minReading": -8.0,
        "maxReading": 8.0
      },
      "G": {
        "sensorName": i18n.t("sensor.names.magnetic_field"),
        "measurementName": i18n.t("sensor.measurements.magnetic_field"),
        "measurementType": "magnetic field",
        "tareable": true,
        "minReading": -80.0,
        "maxReading": 80.0
      },
      "rad": {
        "sensorName": i18n.t("sensor.names.rotary_motion"),
        "measurementName": i18n.t("sensor.measurements.angle"),
        "measurementType": "angle",
        "tareable": true,
        "minReading": -15.0,
        "maxReading": 15.0
      },
      "°": {
        "sensorName": i18n.t("sensor.names.rotary_motion"),
        "measurementName": i18n.t("sensor.measurements.angle"),
        "measurementType": "angle",
        "tareable": true,
        "minReading": -1000.0,
        "maxReading": 1000.0
      },
      "cm": {
        "sensorName": i18n.t("sensor.names.linear_position_sensor"),
        "measurementName": i18n.t("sensor.measurements.position"),
        "measurementType": "position",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 15.0
      },
      "dB": {
        "sensorName": i18n.t("sensor.names.sound_level"),
        "measurementName": i18n.t("sensor.measurements.sound_level"),
        "measurementType": "sound level",
        "tareable": true,
        "minReading": 40.0,
        "maxReading": 110.0
      },
      "dbA": {
        "sensorName": i18n.t("sensor.names.sound_level"),
        "measurementName": i18n.t("sensor.measurements.sound_level"),
        "measurementType": "sound level",
        "tareable": true,
        "minReading": 40.0,
        "maxReading": 110.0
      },
      "rel": {
        "sensorName": i18n.t("sensor.names.spectrophotometer"),
        "measurementName": i18n.t("sensor.measurements.intensity"),
        "measurementType": "intensity",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 1.0
      },
      "Rel": {
        "sensorName": i18n.t("sensor.names.spectrophotometer"),
        "measurementName": i18n.t("sensor.measurements.fluorescence_405_nm"),
        "measurementType": "fluorescence 405 nm",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 1.0
      },
      "Rel.": {
        "sensorName": i18n.t("sensor.names.spectrophotometer"),
        "measurementName": i18n.t("sensor.measurements.fluorescence_500_nm"),
        "measurementType": "fluorescence 500 nm",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 1.0
      },
      "L/s": {
        "sensorName": i18n.t("sensor.names.spirometer"),
        "measurementName": i18n.t("sensor.measurements.flow_rate"),
        "measurementType": "flow rate",
        "tareable": true,
        "minReading": -4.0,
        "maxReading": 4.0
      },
      "mL/s": {
        "sensorName": i18n.t("sensor.names.spirometer"),
        "measurementName": i18n.t("sensor.measurements.flow_rate"),
        "measurementType": "flow rate",
        "tareable": true,
        "minReading": -4000.0,
        "maxReading": 4000.0
      },
      "NTU": {
        "sensorName": i18n.t("sensor.names.turbidity"),
        "measurementName": i18n.t("sensor.measurements.turbidity"),
        "measurementType": "turbidity",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 50.0
      },
      "mW/m²": {
        "sensorName": i18n.t("sensor.names.UV_sensor"),
        "measurementName": i18n.t("sensor.measurements.UV_intensity"),
        "measurementType": "uv intensity",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 20000.0
      },
      "mL": {
        "sensorName": i18n.t("sensor.names.drop_counter"),
        "measurementName": i18n.t("sensor.measurements.volume"),
        "measurementType": "volume",
        "tareable": false,
        "minReading": 0.0,
        "maxReading": 3.0
      },
      "f": {
        "sensorName": i18n.t("sensor.names.altitude"),
        "measurementName": i18n.t("sensor.measurements.altitude"),
        "measurementType": "altitude",
        "tareable": true,
        "minReading": -300.0,
        "maxReading": 300.0
      },
      "mph": {
        "sensorName": i18n.t("sensor.names.anemometer"),
        "measurementName": i18n.t("sensor.measurements.speed"),
        "measurementType": "speed",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 10.0
      },
      "km/h": {
        "sensorName": i18n.t("sensor.names.anemometer"),
        "measurementName": i18n.t("sensor.measurements.speed"),
        "measurementType": "speed",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 20.0
      },
      "knots": {
        "sensorName": i18n.t("sensor.names.anemometer"),
        "measurementName": i18n.t("sensor.measurements.speed"),
        "measurementType": "speed",
        "tareable": true,
        "minReading": 0.0,
        "maxReading": 50.0
      }
    };
  };
});

/*global define: false */

define('models/dual-sensor-connector/modeler',['require','common/lab-modeler-mixin','common/property-description','./metadata','common/state-machine','sensor-connector-interface','./units-definition','models/sensor-common/i18n-sensor-definitions-connector','models/sensor-common/notifier'],function(require) {

  var LabModelerMixin       = require('common/lab-modeler-mixin'),
      PropertyDescription   = require('common/property-description'),
      metadata              = require('./metadata'),
      StateMachine          = require('common/state-machine'),
      SensorConnectorInterface = require('sensor-connector-interface'),
      unitsDefinition       = require('./units-definition'),
      getSensorDefinitions  = require('models/sensor-common/i18n-sensor-definitions-connector'),
      Notifier             = require('models/sensor-common/notifier');

  return function Model(initialProperties, opt) {
    var i18n = opt.i18n,
        notifier = new Notifier(i18n),

        labModelerMixin,
        propertySupport,
        dispatch,
        stateMachine,
        sensorConnectorInterface,
        timeColumn,
        dataColumn,
        dataColumn2,
        selectedSensor,
        selectedSensor2,
        sensorName,
        sensorName2,
        isStopped,
        needsReload,
        time,
        rawSensorValue,
        rawSensorValue2,
        liveSensorValue,
        liveSensorValue2,
        stepCounter,
        isPlayable,
        canTare,
        canTare2,
        canControl,
        hasMultipleSensors,
        isSensorTareable,
        isSensorTareable2,
        message,
        statusDialog,
        statusErrors,
        displayTimePropertyDescription,
        model;

    var defaultSensorReadingDescription = {
          label: i18n.t("sensor.measurements.sensor_reading"),
          unitAbbreviation: "-",
          format: '.2f',
          min: 0,
          max: 10
        };
    var sensorDefinitions = getSensorDefinitions(i18n);

    function updatePropertyRange(property, min, max) {
      var descriptionHash;
      var description;

      descriptionHash = model.getPropertyDescription(property).getHash();
      descriptionHash.min = min;
      descriptionHash.max = max;

      description = new PropertyDescription(unitsDefinition, descriptionHash);
      propertySupport.setPropertyDescription(property, description);
    }

    // Updates min, max of displayTime to be [0..actualDuration]
    function updateDisplayTimeRange() {
      if (model.properties.actualDuration === null || model.properties.actualDuration === Infinity) {
        return;
      }
      updatePropertyRange('displayTime', 0, model.properties.actualDuration);
    }

    function setSensorReadingDescription() {
      var sensorDefinition;
      var description;

      if (dataColumn) {
        sensorDefinition = sensorDefinitions[dataColumn.units];

        if (sensorDefinition) {
          description = {
            label: sensorDefinition.measurementName,
            format: '.2f',
            min: sensorDefinition.minReading,
            max: sensorDefinition.maxReading
          };
          if (unitsDefinition.units[sensorDefinition.measurementType]) {
            description.unitType = sensorDefinition.measurementType;
          } else {
            description.unitAbbreviation = dataColumn.units;
          }
          isSensorTareable = sensorDefinition.tareable;
          sensorName = sensorDefinition.sensorName || (dataColumn.units + " " + i18n.t("sensor.names.sensor"));
        } else {
          description = {
            label: dataColumn.name || i18n.t("sensor.measurements.sensor_reading"),
            unitAbbreviation: dataColumn.units,
            format: '.2f',
            min: 0,
            max: 10
          };
          isSensorTareable = true;
          sensorName = dataColumn.units + " " + i18n.t("sensor.names.sensor");
        }
      } else {
        description = defaultSensorReadingDescription;
        isSensorTareable = false;
        sensorName = i18n.t("sensor.names.no_sensor");
      }

      propertySupport.setPropertyDescription('sensorReading',
        new PropertyDescription(unitsDefinition, description));
      propertySupport.setPropertyDescription('liveSensorReading',
        new PropertyDescription(unitsDefinition, description));
    }

    function setSensorReadingDescription2() {
      var sensorDefinition;
      var description;

      if (dataColumn2) {
        sensorDefinition = sensorDefinitions[dataColumn2.units];

        if (sensorDefinition) {
          description = {
            label: sensorDefinition.measurementName,
            format: '.2f',
            min: sensorDefinition.minReading,
            max: sensorDefinition.maxReading
          };
          if (unitsDefinition.units[sensorDefinition.measurementType]) {
            description.unitType = sensorDefinition.measurementType;
          } else {
            description.unitAbbreviation = dataColumn2.units;
          }
          isSensorTareable2 = sensorDefinition.tareable;
          sensorName2 = sensorDefinition.sensorName || (dataColumn.units + " " + i18n.t("sensor.names.sensor"));
        } else {
          description = {
            label: dataColumn2.name || i18n.t("sensor.measurements.sensor_reading"),
            unitAbbreviation: dataColumn2.units,
            format: '.2f',
            min: 0,
            max: 10
          };
          isSensorTareable2 = true;
          sensorName2 = dataColumn2.units + " " + i18n.t("sensor.names.sensor");
        }
      } else {
        description = defaultSensorReadingDescription;
        isSensorTareable2 = false;
        sensorName2 = i18n.t("sensor.names.no_sensor");
      }

      propertySupport.setPropertyDescription('sensorReading2',
        new PropertyDescription(unitsDefinition, description));
      propertySupport.setPropertyDescription('liveSensorReading2',
        new PropertyDescription(unitsDefinition, description));
    }

    function initializeStateVariables() {
      isStopped = true;
      canControl = sensorConnectorInterface.canControl;
      hasMultipleSensors = false;
      // Set selectedSensor if it hasn't been set yet
      if (typeof(selectedSensor) === "undefined" || selectedSensor === null) {
        selectedSensor = { index: 1 };
      }
      if (typeof(selectedSensor2) === "undefined" || selectedSensor2 === null) {
        selectedSensor2 = { index: 2 };
      }
      stepCounter = 0;
      time = 0;
      statusErrors = 0;
      rawSensorValue = undefined;
      rawSensorValue2 = undefined;
      liveSensorValue = undefined;
      liveSensorValue2 = undefined;
      timeColumn = undefined;
      dataColumn = undefined;
      dataColumn2 = undefined;
    }

    function checkColumnAgainstSelected(dataset, idx) {
        var colCandidate = dataset.columns[idx];
        if (colCandidate && colCandidate.units === selectedSensor.units) {
          selectedSensor.index = idx;
          return true;
        }
        return false;
    }

    function checkColumnAgainstSelected2(dataset, idx) {
        var colCandidate = dataset.columns[idx];
        if (colCandidate && colCandidate.units === selectedSensor2.units) {
          selectedSensor2.index = idx;
          return true;
        }
        return false;
    }

    function hasCollectableSensors(dataset) {
      if (dataset.columns.length < 2) {
        return false;
      }
      if (dataset.columns.length === 2 && !dataset.columns[1].name && !dataset.columns[1].units) {
        return false;
      }
      return true;
    }

    function setColumn() {
      var dataset = sensorConnectorInterface.datasets[0];
      var newDataColumn, newDataColumn2, sIdx;

      hasMultipleSensors = dataset.columns.length > 2;

      // The sensor connector always has column 0 as time
      timeColumn = dataset.columns[0];
      if (sensorConnectorInterface.hasAttachedInterface) {
        if (hasCollectableSensors(dataset) && hasMultipleSensors) {
          if (canControl) {
            message = i18n.t("sensor.messages.ready");
          } else if (sensorConnectorInterface.inControl.clientName) {
            message = i18n.t("sensor.messages.ready_nocontrol", { controlling_client: sensorConnectorInterface.inControl.clientName });
          } else {
            message = i18n.t("sensor.messages.ready_nocontrol_noname");
          }
          isPlayable = true;
        } else {
          message = i18n.t("sensor.messages.no_sensors");
          isPlayable = false;
        }

        // TODO When we want to support multiple sensors, this will have to change.
        // Select the column chosen by the user
        sIdx = selectedSensor.index;
        if (sIdx >= dataset.columns.length && dataset.columns.length > 1) {
          // we seem to be pointing past the number of columns there are. reset to that last column.
          sIdx = dataset.columns.length - 1;
        }
        newDataColumn = dataset.columns[sIdx];
        if (newDataColumn && selectedSensor.units && newDataColumn.units !== selectedSensor.units) {
          // our selected column seems to have changed out from under us.
          // If a sensor was added to the device, it could be one column higher
          if (checkColumnAgainstSelected(dataset, sIdx+1)) {
            newDataColumn = dataset.columns[sIdx+1];
          } else if (sIdx > 1 && checkColumnAgainstSelected(dataset, sIdx-1)) {
            // it wasn't the one after. let's check the one before.
            newDataColumn = dataset.columns[sIdx-1];
          } else {
            // it seems to be none of them. Reset the selected sensor to the first one.
            newDataColumn = dataset.columns[1];
            selectedSensor.index = 1;
          }
        }

        // select the column chosen by the user for the second sensor
        sIdx = selectedSensor2.index;
        if (sIdx >= dataset.columns.length && dataset.columns.length > 2) {
          // we seem to be pointing past the number of columns there are. reset to that last column.
          sIdx = dataset.columns.length - 1;
        }
        newDataColumn2 = dataset.columns[sIdx];
        if (newDataColumn2 && selectedSensor2.units && newDataColumn2.units !== selectedSensor2.units) {
          // our selected column seems to have changed out from under us.
          // If a sensor was added to the device, it could be one column higher
          if (checkColumnAgainstSelected2(dataset, sIdx+1)) {
            newDataColumn2 = dataset.columns[sIdx+1];
          } else if (sIdx > 1 && checkColumnAgainstSelected2(dataset, sIdx-1)) {
            // it wasn't the one after. let's check the one before.
            newDataColumn2 = dataset.columns[sIdx-1];
          } else {
            // it seems to be none of them. Reset the selected sensor to the first one.
            newDataColumn2 = dataset.columns[2];
            selectedSensor2.index = 2;
          }
        }
      } else {
        message = i18n.t("sensor.messages.no_devices");
        isPlayable = false;
      }

      if (newDataColumn && !newDataColumn.name && !newDataColumn.units) {
        // If we don't have a sensor name and units, assume it's not really a sensor
        newDataColumn = null;
      }

      if (newDataColumn2 && !newDataColumn2.name && !newDataColumn2.units) {
        // If we don't have a sensor name and units, assume it's not really a sensor
        newDataColumn2 = null;
      }

      dataColumn = newDataColumn;
      if (dataColumn) {
        selectedSensor.units = dataColumn.units;
      }
      setSensorReadingDescription();

      dataColumn2 = newDataColumn2;
      if (dataColumn2) {
        selectedSensor2.units = dataColumn2.units;
      }
      setSensorReadingDescription2();

      if ( ! dataColumn ) {
        liveSensorValue = undefined;
      }
      if ( ! dataColumn2 ) {
        liveSensorValue2 = undefined;
      }
    }

    function handleData() {
      if (!timeColumn || !dataColumn || !dataColumn2) {
        return;
      }

      var numberOfValues = Math.min(Math.min(timeColumn.data.length, dataColumn.data.length), dataColumn2.data.length);
      for (; stepCounter < numberOfValues; stepCounter++) {
        time = timeColumn.data[stepCounter];

        if (time > model.properties.actualDuration) {
          // Stop, but continue to process any data received (if for no other reason than that
          // subsequent steps aren't happy until data up to the latest timestamp was received)
          model.stop();
        }
        rawSensorValue = dataColumn.data[stepCounter];
        rawSensorValue2 = dataColumn2.data[stepCounter];
        model.updateAllOutputProperties();
        dispatch.tick();
      }
    }

    function isAllColumnDataReceieved(column) {
      return column.receivedValuesTimeStamp >= column.requestedValuesTimeStamp;
    }

    function isAllDataReceived() {
      return isAllColumnDataReceieved(timeColumn) && (! dataColumn || isAllColumnDataReceieved(dataColumn)) && (! dataColumn2 || isAllColumnDataReceieved(dataColumn2));
    }

    function connectedSensors() {
      var sensors = [],
          dataset = sensorConnectorInterface.datasets[0],
          i;

      for (i=0; i < dataset.columns.length; i++) {
        sensors.push({units: dataset.columns[i].units, name: dataset.columns[i].name});
      }
      return sensors;
    }

    model = {

      on: function(type, listener) {
        dispatch.on(type, listener);
      },

      start: function() {
        handle('start');
      },

      stop: function() {
        handle('stop');
      },

      tare: function() {
        var oldPlayable = isPlayable;
        isPlayable = false;
        handle('tare');
        isPlayable = oldPlayable;
      },

      tare2: function() {
        var oldPlayable = isPlayable;
        isPlayable = false;
        handle('tare2');
        isPlayable = oldPlayable;
      },

      willReset: function() {
        dispatch.willReset();
      },

      reset: function() {
        handle('reset');
      },

      reload: function() {
        model.stop();
        model.makeInvalidatingChange(function() {
          needsReload = true;
        });
      },

      isStopped: function() {
        return isStopped;
      },

      stepCounter: function() {
        return stepCounter;
      },

      connectedSensors: connectedSensors,
      getSelectedSensor: function() { return selectedSensor.index; },
      getSelectedSensor2: function() { return selectedSensor2.index; },
      setSelectedSensor: function(sensorIndex) {
        if (selectedSensor.index !== sensorIndex) {
          selectedSensor.index = sensorIndex;
          selectedSensor.units = null;
          model.properties.tareValue = 0; // Also reset our tare value
          setColumn();
        }
      },
      setSelectedSensor2: function(sensorIndex) {
        if (selectedSensor2.index !== sensorIndex) {
          selectedSensor2.index = sensorIndex;
          selectedSensor2.units = null;
          model.properties.tareValue2 = 0; // Also reset our tare value
          setColumn();
        }
      },

      serialize: function () { return ""; }
    };


    stateMachine = new StateMachine({

      notConnected: {
        enterState: function() {
          message = i18n.t("sensor.messages.not_connected");
          statusErrors = 0;
          if (location.protocol === 'https:') {
            sensorConnectorInterface.startPolling(["https://localhost:11181","https://127.0.0.1:11181","https://localhost.ungerdesign.com:11181"], model.properties.clientId, model.properties.clientName);
          } else {
            sensorConnectorInterface.startPolling("http://127.0.0.1:11180", model.properties.clientId, model.properties.clientName);
          }
          this.gotoState('connecting');
        }
      },

      connecting: {
        enterState: function() {
          message = i18n.t("sensor.messages.connecting");
          if (sensorConnectorInterface.isConnected) {
            this.gotoState('connected');
          } else {
            statusDialog = notifier.status(i18n.t("sensor.messages.connection_in_progress"), {dialogClass: 'interactive-dialog no-close'});
          }
        },

        leaveState: function() {
          if (statusDialog) {
            statusDialog.close();
            statusDialog = null;
          }
        },

        statusErrored: function() {
          this.gotoState('initialConnectionFailure');
        },

        connectionTimedOut: function() {
          this.gotoState('initialConnectionFailure');
        },

        launchTimedOut: function() {
          this.gotoState('initialConnectionFailure');
        },

        statusReceived: function() {
          this.gotoState('connected');
        },

        sessionChanged: function() {
          // start a new session, stay connecting...
          sensorConnectorInterface.stopPolling();
          sensorConnectorInterface.startPolling();
        }
      },

      initialConnectionFailure: {
        enterState: function() {
          sensorConnectorInterface.stopPolling();
          message = i18n.t("sensor.messages.connection_failed", { retry_link: "" });
          var dialog_msg = "sensor.messages.connection_failed_alert";
          notifier.alert(i18n.t(dialog_msg, {
                                click_here_link: "<a target='_blank' style='color: #222299;' href='http://sensorconnector.concord.org/'>" +
                                                 i18n.t("sensor.messages.click_here") + "</a>"}), {
            "Try again": function() {
              $(this).dialog("close");
              handle('dismiss');
            }
          },
          {
            close: function() {
              handle('closed');
            }
          });
        },

        // Ignore these in this state
        statusErrored: function() {},
        connectionTimedOut: function() {},
        launchTimedOut: function() {},

        closed: function() {
          message = i18n.t("sensor.messages.connection_failed",
                {
                  retry_link: "<a href='javascript:void(0);' class='retry-link'>" + i18n.t("sensor.messages.connection_failed_retry_link_text") + "</a>"
                });
          // AU: This is kind of ugly and obnoxious, but I was having trouble getting the right context when embedding code directly into the href itself.
          var tryAttachingHandler = function() {
            var links = $('a.retry-link');
            if (links[0]) {
              links.click(function() {
                handle('dismiss');
              });
            } else {
              setTimeout(tryAttachingHandler, 50);
            }
          };
          tryAttachingHandler();
        },

        dismiss: function() {
          this.gotoState('notConnected');
        }
      },

      connected: {
        enterState: function() {
          message = i18n.t("sensor.messages.connected");
          canTare = true;
          canTare2 = true;
          isPlayable = true;
          isStopped = true;

          setColumn();

          if (canControl) {
            this.controlEnabled();
          } else {
            this.controlDisabled();
          }

          if (sensorConnectorInterface.isCollecting) {
            this.gotoState('started');
          }
        },

        leaveState: function() {
          canTare = false;
          canTare2 = false;
          isPlayable = false;
        },

        // Give some feedback on the currently selected column from which data will be collected.
        columnAdded: setColumn,
        columnRemoved: setColumn,
        columnTypeChanged: setColumn,
        columnMoved: setColumn,

        interfaceConnected: setColumn,
        interfaceRemoved: setColumn,

        tare: function() {
          if (dataColumn) {
            model.properties.tareValue = dataColumn.liveValue;
          }
        },

        tare2: function() {
          if (dataColumn2) {
            model.properties.tareValue2 = dataColumn2.liveValue;
          }
        },

        // User requests collection
        start: function() {
          // NOTE. Due to architecture switch mid-way, the sensorConnectorInterface layer is turning the
          // start request into a promise, and we're turning it back to events. The lower layer
          // could just ditch promises and emit the corresponding events with no harm. (The state
          // machine prevents almost every practical scenario where we'd see an out-of-date
          // startRequestFailure event while in a state that would respond to it.)
          sensorConnectorInterface.requestStart().catch(function() {
            handle('startRequestFailed');
          });
          this.gotoState('starting');
        },

        controlEnabled: function() {
          message = i18n.t("sensor.messages.connected");
        },

        controlDisabled: function() {
          if (sensorConnectorInterface.inControl.clientName) {
            message = i18n.t("sensor.messages.connected_start_sensorconnector", { controlling_client: sensorConnectorInterface.inControl.clientName });
          } else {
            message = i18n.t("sensor.messages.connected_start_sensorconnector_noname");
          }
        },

        sessionChanged: function() {
          sensorConnectorInterface.stopPolling();
          sensorConnectorInterface.startPolling();
          this.gotoState('connecting');
        },

        // Collection was started by a third party
        collectionStarted: function() {
          this.gotoState('started');
        }
      },

      starting: {
        enterState: function() {
          message = i18n.t("sensor.messages.starting_data_collection");
          isStopped = false;
          var self = this;
          this._startTimerId = setTimeout(5000, function() {
            self.gotoState('startRequestFailed');
          });
        },

        leaveState: function() {
          clearTimeout(this._startTimerId);
        },

        startRequestFailed: function() {
          this.gotoState('errorStarting');
        },

        collectionStarted: function() {
          this.gotoState('started');
        }
      },

      errorStarting: {
        enterState: function() {
          message = i18n.t("sensor.messages.error_starting_data_collection");
          isStopped = true;

          notifier.alert(i18n.t("sensor.messages.error_starting_data_collection_alert"), {
            OK: function() {
              $(this).dialog("close");
              handle('dismissErrorStarting');
            }
          });
        },

        collectionStarted: function() {
          this.gotoState('started');
        },

        dismissErrorStarting: function() {
          this.gotoState('connected');
        }
      },

      started: {
        enterState: function() {
          isStopped = false;
          setColumn();
          isPlayable = false;
          if (canControl) {
            message = i18n.t("sensor.messages.collecting_data");
          } else {
            message = i18n.t("sensor.messages.collecting_data_stop_sensorconnector");
          }

          // Check, just in case. Specifically, when errorStopping transitions here, collection
          // might have stopped in the meantime.
          if ( ! sensorConnectorInterface.isCollecting ) {
            this.gotoState('stopped');
          }

          if ( (! dataColumn)  || (! dataColumn2)) {
            this.gotoState('startedWithNoDataColumn');
          }
        },

        data: handleData,

        controlEnabled: function() {
          message = i18n.t("sensor.messages.collecting_data");
        },

        controlDisabled: function() {
          message = i18n.t("sensor.messages.collecting_data_stop_sensorconnector");
        },

        stop: function() {
          sensorConnectorInterface.requestStop().catch(function() {
            handle('stopRequestFailed');
          });
          this.gotoState('stopping');
        },

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        }
      },

      // This can happen.
      startedWithNoDataColumn: {
        enterState: function() {
          message = i18n.t("sensor.messages.no_data");

          sensorConnectorInterface.requestStop();
          notifier.alert(i18n.t("sensor.messages.no_data_alert"), {
            OK: function() {
              $(this).dialog("close");
            }
          });
        },

        collectionStopped: function() {
          this.gotoState('stoppedWithNoDataColumn');
        }
      },

      stoppedWithNoDataColumn: {
        enterState: function() {
          if (isAllDataReceived()) {
            this.gotoState('connected');
          }
        },

        data: function() {
          if (isAllDataReceived()) {
            this.gotoState('connected');
          }
        }
      },

      stopping: {
        enterState: function() {
          message = i18n.t("sensor.messages.stopping_data_collection");
        },

        data: handleData,

        stopRequestFailed: function() {
          this.gotoState('errorStopping');
        },

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        }
      },

      errorStopping: {
        enterState: function() {
          message = i18n.t("sensor.messages.error_stopping_data_collection");
          notifier.alert(i18n.t("sensor.messages.error_stopping_data_collection_alert"), {
            OK: function() {
              $(this).dialog("close");
              handle('dismissErrorStopping');
            }
          });
        },

        data: handleData,

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        },

        dismissErrorStopping: function() {
          this.gotoState('started');
        }
      },

      // The device reports the stop of data collection before all data can be received.
      collectionStopped: {
        enterState: function() {
          message = i18n.t("sensor.messages.data_collection_stopped");
          if (isAllDataReceived()) {
            this.gotoState('collectionComplete');
          }
        },

        data: function() {
          handleData();
          if (isAllDataReceived()) {
            this.gotoState('collectionComplete');
          }
        }
      },

      collectionComplete: {
        enterState: function() {
          message = i18n.t("sensor.messages.data_collection_complete");
          isStopped = true;
          dispatch.stop();
        },

        reset: function() {
          initializeStateVariables();
          setSensorReadingDescription();
          setSensorReadingDescription2();
          this.gotoState('connecting');
          dispatch.reset();
        }
      },

      disconnected: {
        enterState: function() {
          message = i18n.t("sensor.messages.disconnected");
          sensorConnectorInterface.stopPolling();
          var self = this;
          setTimeout(function() {
            self.gotoState('notConnected');
          }, 2000);
        }
      }
    });

    // Automatically wrap all event handlers invocations with makeInvalidatingChange so that
    // outputs update from closure variable state automatically.
    function handle(eventName) {
      var args = Array.prototype.slice.call(arguments, 0);

      model.makeInvalidatingChange(function() {
        var handled = stateMachine.handleEvent.apply(stateMachine, args);

        if ( ! handled ) {
          // special handling of any events not handled by the current state:
          if (eventName === 'connectionTimedOut') {
            stateMachine.gotoState('disconnected');
          } else if (eventName === 'sessionChanged') {
            sensorConnectorInterface.stopPolling();
            stateMachine.gotoState('disconnected');
          } else if (eventName === 'statusErrored') {
            stateMachine.gotoState('initialConnectionFailure');
          }
        }
      });
    }

    sensorConnectorInterface = new SensorConnectorInterface();

    // At least for now, dispatch every interface event to the state machine.
    sensorConnectorInterface.on('*', function() {
      var args = Array.prototype.slice.call(arguments, 0);
      handle.apply(null, [this.event].concat(args));
    });

    // Also, handle "live values" every time they are received.
    sensorConnectorInterface.on('statusReceived', function() {
      if (dataColumn) {
        model.makeInvalidatingChange(function() {
          liveSensorValue = dataColumn.liveValue;
        });
      }
      if (dataColumn2) {
        model.makeInvalidatingChange(function() {
          liveSensorValue2 = dataColumn2.liveValue;
        });
      }
    });

    sensorConnectorInterface.on('controlEnabled',  function() {
      canControl = true;
    });

    sensorConnectorInterface.on('controlDisabled', function() {
      canControl = false;
    });

    labModelerMixin = new LabModelerMixin({
      metadata: metadata,
      setters: {},
      unitsDefinition: unitsDefinition,
      initialProperties: initialProperties,
      usePlaybackSupport: false
    });

    propertySupport = labModelerMixin.propertySupport;
    dispatch = labModelerMixin.dispatchSupport;
    dispatch.addEventTypes("tick", "play", "stop", "tickStart", "tickEnd");

    labModelerMixin.mixInto(model);

    initializeStateVariables();

    if (model.properties.useRandomClientId) {
      model.properties.clientId = Math.floor((1 + Math.random()) * 0x100000000).toString(16); // a 9-digit hex string that always starts with 1
    }

    model.addObserver('clientName', function() {
      sensorConnectorInterface.clientName = model.properties.clientName;
    });

    model.defineOutput('time', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.2f'
    }, function() {
      return time;
    });

    model.defineOutput('displayTime', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.2f'
    }, function() {
      return time;
    });

    displayTimePropertyDescription = model.getPropertyDescription('displayTime');

    model.defineOutput('sensorReading', defaultSensorReadingDescription, function() {
      if (rawSensorValue == null) {
        return rawSensorValue;
      }
      return rawSensorValue - model.properties.tareValue;
    });

    model.defineOutput('sensorReading2', defaultSensorReadingDescription, function() {
      if (rawSensorValue2 == null) {
        return rawSensorValue2;
      }
      return rawSensorValue2 - model.properties.tareValue2;
    });

    /* Need custom implementation of duration options, because we don't use playbackSupport
      (and playbackSupport's implementation is designed for simulation models that have a
      fixed time per tick */

    model.defineOutput('durationOptions', {}, function() {
      return [1, 5, 10, 15, 20, 30, 45, 60];
    });

    model.formatTime = function(time) {
      return  displayTimePropertyDescription.format(time);
    };

    model.defineOutput('actualUseDuration', {}, labModelerMixin.computeActualUseDuration);

    model.defineOutput('actualDuration', {
      label: "Experiment duration",
      unitType: 'time',
      format: 'f'
    }, labModelerMixin.computeActualDuration);

    // Because sensorReading updates are batched and delivered much later than the live sensor value
    // from the sensor status response, we define a separate liveSensorReading output that can be
    // updated every time the status is polled.
    model.defineOutput('liveSensorReading', defaultSensorReadingDescription, function() {
      if (liveSensorValue == null) {
        return liveSensorValue;
      }
      return liveSensorValue - model.properties.tareValue;
    });

    model.defineOutput('liveSensorReading2', defaultSensorReadingDescription, function() {
      if (liveSensorValue2 == null) {
        return liveSensorValue2;
      }
      return liveSensorValue2 - model.properties.tareValue2;
    });

    model.defineOutput('sensorName', {
      label: "Sensor Name"
    }, function() {
      return sensorName;
    });

    model.defineOutput('sensorName2', {
      label: "2nd Sensor Name"
    }, function() {
      return sensorName2;
    });

    model.defineOutput('isStopped', {
      label: "Stopped?"
    }, function() {
      return isStopped;
    });

    // TODO. We need a way to make "model-writable" read only properties.
    model.defineOutput('isPlayable', {
      label: "Startable?"
    }, function() {
      return isPlayable;
    });

    model.defineOutput('hasPlayed', {
      label: "Has successfully collected data?"
    }, function() {
      return stepCounter > 0;
    });

    model.defineOutput('canTare', {
      label: "Can set a tare value?"
    }, function() {
      return canTare && isSensorTareable;
    });

    model.defineOutput('canTare2', {
      label: "Can set a tare value?"
    }, function() {
      return canTare2 && isSensorTareable2;
    });

    model.defineOutput('canControl', {
      label: "Can remotely start/stop the Sensor Connector?"
    }, function() {
      return canControl;
    });

    model.defineOutput('hasMultipleSensors', {
      label: "Are multiple sensors connected to the Sensor Connector?"
    }, function() {
      return hasMultipleSensors;
    });

    model.defineOutput('needsReload', {
      label: "Needs Reload?"
    }, function() {
      return needsReload;
    });

    model.defineOutput('message', {
      label: "User Message"
    }, function() {
      return message;
    });

    // Clean up state before we go
    // TODO
    model.on('willReset.model', function() {
      sensorConnectorInterface.stopPolling();
      if (sensorConnectorInterface.isCollecting) {
        sensorConnectorInterface.requestStop();
      }
    });

    model.addObserver('actualDuration', updateDisplayTimeRange);
    updateDisplayTimeRange();

    model.updateAllOutputProperties();
    stateMachine.gotoState('notConnected');

    return model;
  };
});

define('models/dual-sensor-connector/view',['require','common/views/numeric-output-view','common/controllers/basic-dialog','models/sensor-common/i18n-sensor-definitions-connector','common/views/view-state'],function(require) {

  var NumericOutputView    = require('common/views/numeric-output-view'),
      BasicDialog          = require('common/controllers/basic-dialog'),
      getSensorDefinitions = require('models/sensor-common/i18n-sensor-definitions-connector'),
      viewState = require('common/views/view-state');

  return function(model, modelUrl, i18n) {

    // TODO use the formatter from the property description. Right now, it automatically adds
    // units to the returned string (which we don't want here).
    var format = d3.format('.2f');
    var sensorReadingView;
    var sensorReadingView2;
    var view;

    var sensorDefinitions = getSensorDefinitions(i18n);

    function setCanTareState() {
      if (model.properties.canTare) {
        viewState.enableView(view.$zeroButton);
      } else {
        viewState.disableView(view.$zeroButton);
      }
    }

    function setCanTare2State() {
      if (model.properties.canTare2) {
        viewState.enableView(view.$zeroButton2);
      } else {
        viewState.disableView(view.$zeroButton2);
      }
    }

    function setCanConnectState() {
      // if (model.properties.canConnect) {
      //   viewState.enableView(view.$connectButton);
      // } else {
      //   viewState.disableView(view.$connectButton);
      // }
    }

    function setHasMultipleSensorsState() {
      if (model.properties.hasMultipleSensors && model.properties.isPlayable) {
        viewState.enableView(view.$selectSensorButton);
        viewState.enableView(view.$selectSensorButton2);
      } else {
        viewState.disableView(view.$selectSensorButton);
        viewState.disableView(view.$selectSensorButton2);
      }
    }

    function setMessageText() {
      view.$message.html(model.properties.message);
    }

    function setupModelObservers() {
      model.addObserver('canTare', setCanTareState);
      model.addObserver('canTare2', setCanTare2State);
      setCanTareState();
      setCanTare2State();

      model.addObserver('canConnect', setCanConnectState);
      setCanConnectState();

      model.addObserver('hasMultipleSensors', setHasMultipleSensorsState);
      model.addObserver('isPlayable', setHasMultipleSensorsState);
      setHasMultipleSensorsState();

      model.addObserver('message', setMessageText);
      setMessageText();
    }

    function chooseSensorPopup1() {
      chooseSensorPopup(false);
    }

    function chooseSensorPopup2() {
      chooseSensorPopup(true);
    }

    function chooseSensorPopup(second) {
      second = second || false;
      var dialog = new BasicDialog({
        width: "60%",
        dialogClass: 'interactive-dialog no-close',
        closeOnEscape: false,
        title: i18n.t("sensor.choose_sensor_title"),
        buttons: {
          OK: function() {
            console.log("OK Clicked");
            $(this).dialog("close");
            // Change the model's selected sensor
            if (second) {
              model.setSelectedSensor2($(this).find('input:checked').val());
            } else {
              model.setSelectedSensor($(this).find('input:checked').val());
            }
          },
          Cancel: function() {
            console.log("Cancel Clicked");
            $(this).dialog("close");
          }
        }
      }, i18n);
      var content = "",
          label = "",
          sensors = model.connectedSensors(),
          first = true,
          selectedSensor = (second ? model.getSelectedSensor2() : model.getSelectedSensor()),
          i, checked, sensorDef;
      for (i = 0; i < sensors.length; i++) {
        if (sensors[i].units !== 's') {
          checked = "";
          if (sensors[i].name) {
            sensorDef = { measurementName: sensors[i].name };
          } else {
            sensorDef = sensorDefinitions[sensors[i].units];
            if (!sensorDef) {
              sensorDef = { measurementName: "Unknown" };
            }
          }
          label = sensorDef.measurementName + " (" + sensors[i].units + ")";
          if (selectedSensor == i || (first && selectedSensor == -1)) {
            checked = "checked ";
          };
          content += "<input type='radio' name='selected-sensor-index' value='" + i + "' " + checked + "/>" + label + "<br/>";
          first = false;
        }
      }
      dialog.setContent(content);
      dialog.open();
    }

    return view = {
      $el: $("<div id='model-container' class='container sensor-model-container' />"),
      remoteAddress: null,

      bindModel: function(newModel, newModelUrl) {
        modelUrl = newModelUrl || modelUrl;
        model = newModel || model;

        setupModelObservers();
      },

      getHeightForWidth: function() {
        return "2.8em";
      },

      // called once we're in the DOM
      setup: function() {
        view.$el.empty();
        view.$controlsContainer = $("<div></div>");
        view.$statusContainer = $("<div></div>");
        // view.$addressInput = $("<div class='address-input'><input type='text' name='address-input' placeholder='address of LabQuest2'></input></div>");
        sensorReadingView = new NumericOutputView({
          id: 'sensor-value-view',
          label: i18n.t("sensor.reading"),
          units: model.getPropertyDescription('sensorReading').getUnitAbbreviation()
        });
        sensorReadingView2 = new NumericOutputView({
          id: 'sensor-value-view-2',
          label: i18n.t("sensor.reading"),
          units: model.getPropertyDescription('sensorReading2').getUnitAbbreviation()
        });

        // view.$connectButton = $("<div class='interactive-button'><button>Connect</button></div>");
        view.$zeroButton = $("<div class='interactive-button'><button>" + i18n.t("sensor.zero") + "</button></div>");
        view.$zeroButton2 = $("<div class='interactive-button'><button>" + i18n.t("sensor.zero") + "</button></div>");
        view.$selectSensorButton = $("<div class='interactive-button'><button>" + i18n.t("sensor.select_sensor") + "</button></div>");
        view.$selectSensorButton2 = $("<div class='interactive-button'><button>" + i18n.t("sensor.select_sensor") + "</button></div>");
        view.$message = $("<div class='message'></div>");
        view.$sensorReading = sensorReadingView.render().addClass("horizontal");
        view.$sensorReading2 = sensorReadingView2.render().addClass("horizontal");

        view.$controlsContainer
          // .append(view.$addressInput)
          // .append(view.$connectButton)
          .append(view.$sensorReading)
          .append(view.$zeroButton)
          .append(view.$selectSensorButton)
          .append(view.$sensorReading2)
          .append(view.$zeroButton2)
          .append(view.$selectSensorButton2);

        view.$statusContainer
          .append(view.$message);

        view.$el.css('zIndex', 4)
          .append(view.$controlsContainer)
          .append(view.$statusContainer);

        view.$controlsContainer.find('div').addClass('component component-spacing');
        view.$statusContainer.find('div').addClass('component component-spacing');

        sensorReadingView.resize();
        sensorReadingView2.resize();
        setupModelObservers();

        // if (typeof(view.remoteAddress) == "string") {
        //   view.$addressInput.find('input').val(view.remoteAddress);
        // }
        // view.$addressInput.find('input').on('change', function() {
        //   view.remoteAddress = $(this).val();
        // });

        // view.$connectButton.on('click', 'button', function() {
        //   model.connect(view.$addressInput.find('input').val());
        // });
        view.$zeroButton.on('click', 'button', model.tare);
        view.$zeroButton2.on('click', 'button', model.tare2);
        view.$selectSensorButton.on('click', 'button', chooseSensorPopup1);
        view.$selectSensorButton2.on('click', 'button', chooseSensorPopup2);
      },

      resize: function() {
        if (sensorReadingView) {
          sensorReadingView.resize();
        }
        if (sensorReadingView2) {
          sensorReadingView2.resize();
        }
      },

      repaint: function() {},

      setFocus: function () {},

      updateUnits: function(units) {
        sensorReadingView.updateUnits(units);
        if (model.properties.liveSensorReading == null) {
          sensorReadingView.hideUnits();
        }
      },

      updateUnits2: function(units) {
        sensorReadingView2.updateUnits(units);
        if (model.properties.liveSensorReading2 == null) {
          sensorReadingView2.hideUnits();
        }
      },

      update: function() {
        if (model.properties.liveSensorReading == null) {
          sensorReadingView.update("");
          sensorReadingView.hideUnits();
        } else {
          sensorReadingView.update(format(model.properties.liveSensorReading));
          sensorReadingView.showUnits();
        }
        if (model.properties.liveSensorReading2 == null) {
          sensorReadingView2.update("");
          sensorReadingView2.hideUnits();
        } else {
          sensorReadingView2.update(format(model.properties.liveSensorReading2));
          sensorReadingView2.showUnits();
        }
      }
    };
  };
});

define('models/dual-sensor-connector/scripting-api',[],function () {
  /**
    Define the model-specific Dual Sensor Connector scripting API used by 'action' scripts on interactive elements.

    The universal Interactive scripting API is extended with the properties of the
    object below which will be exposed to the interactive's 'action' scripts as if
    they were local vars. All other names (including all globals, but excluding
    Javascript builtins) will be unavailable in the script context; and scripts
    are run in strict mode so they don't accidentally expose or read globals.

    @param: parent Common Scripting API
  */
  return function DualSensorConnectorScriptingAPI (parent) {
    return {
      /**
       * Reset the model. The interactives controller will emit a 'willResetModel'.
       * The willResetModel observers can ask to wait for asynchronous confirmation before
       * the model is actually reset.
       * Note that the effect would be almost the same like after reload operation. However
       * the mechanism under the hood is completely different. The biggest difference for
       * authoring is that when .resetModel() is called, the "onLoad" script won't be
       * executed again. It may be also faster than reload.
       * @param  {object} options hash of options, supported properties:
       *                         * propertiesToRetain - a list of properties to save before
       *                           the model reset and restore after reset.
       *                         * cause - cause of the reset action.
       */
      resetModel: function resetModel(options) {
        parent.intController.resetModel(options);
      }
    };
  };
});

/*global define */

define('models/dual-sensor-connector/controller',['require','common/controllers/model-controller','./modeler','./view','./scripting-api'],function (require) {
  // Dependencies.
  var ModelController   = require('common/controllers/model-controller'),
      Model             = require('./modeler'),
      ModelContainer    = require('./view'),
      ScriptingAPI      = require('./scripting-api');

  return function (modelUrl, modelOptions, interactiveController) {
    var controller = new ModelController(modelUrl, modelOptions, interactiveController,
      Model, ModelContainer, ScriptingAPI);

    // Note to self: modelController doesn't emit modelLoaded when the model first loads.
    // This was unexpected...

    function setupModelObservers() {
      var model = controller.model;

      model.addObserver('liveSensorReading', function() {
        controller.updateView();
      });

      model.addObserver('liveSensorReading2', function() {
        controller.updateView();
      });

      model.addObserver('needsReload', function() {
        if (model.properties.needsReload) {
          interactiveController.reloadModel();
        }
      });

      model.addPropertyDescriptionObserver('sensorReading', function() {
        var description = model.getPropertyDescription('sensorReading');
        var view = controller.modelContainer;

        view.updateUnits(description.getUnitAbbreviation());
      });

      model.addPropertyDescriptionObserver('sensorReading2', function() {
        var description = model.getPropertyDescription('sensorReading2');
        var view = controller.modelContainer;

        view.updateUnits2(description.getUnitAbbreviation());
      });
    }

    interactiveController.on('modelLoaded.dual-sensor-connector-model-controller', setupModelObservers);

    interactiveController.on('modelReset.dual-sensor-connector-model-controller', function() {
      controller.model.set('isNewRunInProgress', false);
    });

    interactiveController.on('willResetModel', function() {
      controller.model.set('isNewRunInProgress', true);
      controller.model.willReset();
    });

    return controller;
  };
});

/*global define: false */

define('models/sensor-connector/metadata',[],function() {

  return {
    mainProperties: {
      type: {
        defaultValue: "sensor-connector",
        immutable: true
      },
      sensorType: {
        defaultValue: null
      },
      tareValue: {
        defaultValue: 0
      },
      clientId: {
        defaultValue: null
      },
      clientName: {
        defaultValue: null
      },
      useRandomClientId: {
        defaultValue: false
      },
      useDuration: {
        defaultValue: 'codap',
        storeInTickHistory: false,
        validate: function(value) {
          if (value === true || value === false || value === 'codap') {
            return value;
          }
          throw new Error("Invalid 'useDuration' value: " + value);
        },
      },
      requestedDuration: {
        defaultValue: 10,
        storeInTickHistory: false
      }
    },
    viewOptions: {
      showClock: {
        defaultValue: true,
        propertyChangeInvalidates: false
      },
      controlButtons: {
        defaultValue: "play_reset",
        propertyChangeInvalidates: false
      },
      controlButtonStyle: {
        defaultValue: "video",
        propertyChangeInvalidates: false,
        serialize: false
      }
    }
  };
});

define('models/sensor-connector/units-definition',[],function() {
  return {
    units: {
      time: {
        name: "second",
        pluralName: "seconds",
        symbol: "s"
      }
    }
  };
});

/*global define: false */

define('models/sensor-connector/modeler',['require','common/lab-modeler-mixin','common/property-description','./metadata','common/state-machine','sensor-connector-interface','./units-definition','models/sensor-common/i18n-sensor-definitions-connector','models/sensor-common/notifier'],function(require) {

  var LabModelerMixin       = require('common/lab-modeler-mixin'),
      PropertyDescription   = require('common/property-description'),
      metadata              = require('./metadata'),
      StateMachine          = require('common/state-machine'),
      SensorConnectorInterface = require('sensor-connector-interface'),
      unitsDefinition       = require('./units-definition'),
      getSensorDefinitions  = require('models/sensor-common/i18n-sensor-definitions-connector'),
      Notifier             = require('models/sensor-common/notifier');

  return function Model(initialProperties, opt) {
    var i18n = opt.i18n,
        notifier = new Notifier(i18n),

        labModelerMixin,
        propertySupport,
        dispatch,
        stateMachine,
        sensorConnectorInterface,
        timeColumn,
        dataColumn,
        selectedSensor,
        sensorName,
        isStopped,
        needsReload,
        time,
        rawSensorValue,
        liveSensorValue,
        stepCounter,
        isPlayable,
        canTare,
        canControl,
        hasMultipleSensors,
        isSensorTareable,
        message,
        statusDialog,
        statusErrors,
        displayTimePropertyDescription,
        model;

    var defaultSensorReadingDescription = {
      label: i18n.t("sensor.measurements.sensor_reading"),
      unitAbbreviation: "-",
      format: '.2f',
      min: 0,
      max: 10
    };
    var sensorDefinitions = getSensorDefinitions(i18n);

    function updatePropertyRange(property, min, max) {
      var descriptionHash;
      var description;

      descriptionHash = model.getPropertyDescription(property).getHash();
      descriptionHash.min = min;
      descriptionHash.max = max;

      description = new PropertyDescription(unitsDefinition, descriptionHash);
      propertySupport.setPropertyDescription(property, description);
    }

    // Updates min, max of displayTime to be [0..actualDuration]
    function updateDisplayTimeRange() {
      if (model.properties.actualDuration === null || model.properties.actualDuration === Infinity) {
        return;
      }
      updatePropertyRange('displayTime', 0, model.properties.actualDuration);
    }

    function setSensorReadingDescription() {
      var sensorDefinition;
      var description;

      if (dataColumn) {
        sensorDefinition = sensorDefinitions[dataColumn.units];

        if (sensorDefinition) {
          description = {
            label: sensorDefinition.measurementName,
            format: '.2f',
            min: sensorDefinition.minReading,
            max: sensorDefinition.maxReading
          };
          if (unitsDefinition.units[sensorDefinition.measurementType]) {
            description.unitType = sensorDefinition.measurementType;
          } else {
            description.unitAbbreviation = dataColumn.units;
          }
          isSensorTareable = sensorDefinition.tareable;
          sensorName = sensorDefinition.sensorName || (dataColumn.units + " sensor");
        } else {
          description = {
            label: dataColumn.name || i18n.t("sensor.measurements.sensor_reading"),
            unitAbbreviation: dataColumn.units,
            format: '.2f',
            min: 0,
            max: 10
          };
          isSensorTareable = true;
          sensorName = dataColumn.units + " sensor";
        }
      } else {
        description = defaultSensorReadingDescription;
        isSensorTareable = false;
        sensorName = "(no sensor)";
      }

      propertySupport.setPropertyDescription('sensorReading',
        new PropertyDescription(unitsDefinition, description));
      propertySupport.setPropertyDescription('liveSensorReading',
        new PropertyDescription(unitsDefinition, description));
    }

    function initializeStateVariables() {
      isStopped = true;
      canControl = sensorConnectorInterface.canControl;
      hasMultipleSensors = false;
      // Set selectedSensor if it hasn't been set yet
      if (typeof(selectedSensor) === "undefined" || selectedSensor === null) {
        selectedSensor = { index: 1 };
      }
      stepCounter = 0;
      time = 0;
      statusErrors = 0;
      rawSensorValue = undefined;
      liveSensorValue = undefined;
      timeColumn = undefined;
      dataColumn = undefined;
    }

    function checkColumnAgainstSelected(dataset, idx) {
        var colCandidate = dataset.columns[idx];
        if (colCandidate && colCandidate.units === selectedSensor.units) {
          selectedSensor.index = idx;
          return true;
        }
        return false;
    }

    function hasCollectableSensors(dataset) {
      if (dataset.columns.length < 2) {
        return false;
      }
      if (dataset.columns.length === 2 && !dataset.columns[1].name && !dataset.columns[1].units) {
        return false;
      }
      return true;
    }

    function setColumn() {
      var dataset = sensorConnectorInterface.datasets[0];
      var newDataColumn, sIdx;

      hasMultipleSensors = dataset.columns.length > 2;

      // The sensor connector always has column 0 as time
      timeColumn = dataset.columns[0];
      if (sensorConnectorInterface.hasAttachedInterface) {
        if (hasCollectableSensors(dataset)) {
          if (canControl) {
            message = i18n.t("sensor.messages.ready");
          } else if (sensorConnectorInterface.inControl.clientName) {
            message = i18n.t("sensor.messages.ready_nocontrol", { controlling_client: sensorConnectorInterface.inControl.clientName });
          } else {
            message = i18n.t("sensor.messages.ready_nocontrol_noname");
          }
          isPlayable = true;
        } else {
          message = i18n.t("sensor.messages.no_sensors");
          isPlayable = false;
        }

        // TODO When we want to support multiple sensors, this will have to change.
        // Select the column chosen by the user
        sIdx = selectedSensor.index;
        if (sIdx >= dataset.columns.length && dataset.columns.length > 1) {
          // we seem to be pointing past the number of columns there are. reset to that last column.
          sIdx = dataset.columns.length - 1;
        }
        newDataColumn = dataset.columns[sIdx];
        if (newDataColumn && selectedSensor.units && newDataColumn.units !== selectedSensor.units) {
          // our selected column seems to have changed out from under us.
          // If a sensor was added to the device, it could be one column higher
          if (checkColumnAgainstSelected(dataset, sIdx+1)) {
            newDataColumn = dataset.columns[sIdx+1];
          } else if (sIdx > 1 && checkColumnAgainstSelected(dataset, sIdx-1)) {
            // it wasn't the one after. let's check the one before.
            newDataColumn = dataset.columns[sIdx-1];
          } else {
            // it seems to be none of them. Reset the selected sensor to the first one.
            newDataColumn = dataset.columns[1];
            selectedSensor.index = 1;
          }
        }
      } else {
        message = i18n.t("sensor.messages.no_devices");
        isPlayable = false;
      }

      if (newDataColumn && !newDataColumn.name && !newDataColumn.units) {
        // If we don't have a sensor name and units, assume it's not really a sensor
        newDataColumn = null;
      }

      dataColumn = newDataColumn;
      if (dataColumn) {
        selectedSensor.units = dataColumn.units;
      }
      setSensorReadingDescription();

      if ( ! dataColumn ) {
        liveSensorValue = undefined;
      }
    }

    function handleData() {
      if (!timeColumn || !dataColumn) {
        return;
      }

      var numberOfValues = Math.min(timeColumn.data.length, dataColumn.data.length);
      for (; stepCounter < numberOfValues; stepCounter++) {
        time = timeColumn.data[stepCounter];

        if (time > model.properties.actualDuration) {
          // Stop, but continue to process any data received (if for no other reason than that
          // subsequent steps aren't happy until data up to the latest timestamp was received)
          model.stop();
        }
        rawSensorValue = dataColumn.data[stepCounter];
        model.updateAllOutputProperties();
        dispatch.tick();
      }
    }

    function isAllColumnDataReceieved(column) {
      return column.receivedValuesTimeStamp >= column.requestedValuesTimeStamp;
    }

    function isAllDataReceived() {
      return isAllColumnDataReceieved(timeColumn) && (! dataColumn || isAllColumnDataReceieved(dataColumn));
    }

    function connectedSensors() {
      var sensors = [],
          dataset = sensorConnectorInterface.datasets[0],
          i;

      for (i=0; i < dataset.columns.length; i++) {
        sensors.push({units: dataset.columns[i].units, name: dataset.columns[i].name});
      }
      return sensors;
    }

    model = {

      on: function(type, listener) {
        dispatch.on(type, listener);
      },

      start: function() {
        handle('start');
      },

      stop: function() {
        handle('stop');
      },

      tare: function() {
        var oldPlayable = isPlayable;
        isPlayable = false;
        handle('tare');
        isPlayable = oldPlayable;
      },

      willReset: function() {
        dispatch.willReset();
      },

      reset: function() {
        handle('reset');
      },

      reload: function() {
        model.stop();
        model.makeInvalidatingChange(function() {
          needsReload = true;
        });
      },

      isStopped: function() {
        return isStopped;
      },

      stepCounter: function() {
        return stepCounter;
      },

      connectedSensors: connectedSensors,
      getSelectedSensor: function() { return selectedSensor.index; },
      setSelectedSensor: function(sensorIndex) {
        if (selectedSensor.index != sensorIndex) {
          selectedSensor.index = sensorIndex;
          selectedSensor.units = null;
          model.properties.tareValue = 0; // Also reset our tare value
          setColumn();
        }
      },

      serialize: function () { return ""; }
    };


    stateMachine = new StateMachine({

      notConnected: {
        enterState: function() {
          message = i18n.t("sensor.messages.not_connected");
          statusErrors = 0;
          if (location.protocol === 'https:') {
            sensorConnectorInterface.startPolling(["https://localhost:11181","https://127.0.0.1:11181","https://localhost.ungerdesign.com:11181"], model.properties.clientId, model.properties.clientName);
          } else {
            sensorConnectorInterface.startPolling("http://127.0.0.1:11180", model.properties.clientId, model.properties.clientName);
          }
          this.gotoState('connecting');
        }
      },

      connecting: {
        enterState: function() {
          message = i18n.t("sensor.messages.connecting");
          if (sensorConnectorInterface.isConnected) {
            this.gotoState('connected');
          } else {
            statusDialog = notifier.status(i18n.t("sensor.messages.connection_in_progress"), {dialogClass: 'interactive-dialog no-close'});
          }
        },

        leaveState: function() {
          if (statusDialog) {
            statusDialog.close();
            statusDialog = null;
          }
        },

        statusErrored: function() {
          this.gotoState('initialConnectionFailure');
        },

        connectionTimedOut: function() {
          this.gotoState('initialConnectionFailure');
        },

        launchTimedOut: function() {
          this.gotoState('initialConnectionFailure');
        },

        statusReceived: function() {
          this.gotoState('connected');
        },

        sessionChanged: function() {
          // start a new session, stay connecting...
          sensorConnectorInterface.stopPolling();
          sensorConnectorInterface.startPolling();
        }
      },

      initialConnectionFailure: {
        enterState: function() {
          sensorConnectorInterface.stopPolling();
          message = i18n.t("sensor.messages.connection_failed", { retry_link: "" });
          var dialog_msg = "sensor.messages.connection_failed_alert";
          notifier.alert(i18n.t(dialog_msg, {
                                click_here_link: "<a target='_blank' style='color: #222299;' href='http://sensorconnector.concord.org/'>" +
                                                 i18n.t("sensor.messages.click_here") + "</a>"}), {
            "Try again": function() {
              $(this).dialog("close");
              handle('dismiss');
            }
          },
          {
            close: function() {
              handle('closed');
            }
          });
        },

        // Ignore these in this state
        statusErrored: function() {},
        connectionTimedOut: function() {},
        launchTimedOut: function() {},

        closed: function() {
          message = i18n.t("sensor.messages.connection_failed",
                {
                  retry_link: "<a href='javascript:void(0);' class='retry-link'>" + i18n.t("sensor.messages.connection_failed_retry_link_text") + "</a>"
                });
          // AU: This is kind of ugly and obnoxious, but I was having trouble getting the right context when embedding code directly into the href itself.
          var tryAttachingHandler = function() {
            var links = $('a.retry-link');
            if (links[0]) {
              links.click(function() {
                handle('dismiss');
              });
            } else {
              setTimeout(tryAttachingHandler, 50);
            }
          };
          tryAttachingHandler();
        },

        dismiss: function() {
          this.gotoState('notConnected');
        }
      },

      connected: {
        enterState: function() {
          message = i18n.t("sensor.messages.connected");
          canTare = true;
          isPlayable = true;
          isStopped = true;

          setColumn();

          if (canControl) {
            this.controlEnabled();
          } else {
            this.controlDisabled();
          }

          if (sensorConnectorInterface.isCollecting) {
            this.gotoState('started');
          }
        },

        leaveState: function() {
          canTare = false;
          isPlayable = false;
        },

        // Give some feedback on the currently selected column from which data will be collected.
        columnAdded: setColumn,
        columnRemoved: setColumn,
        columnTypeChanged: setColumn,
        columnMoved: setColumn,

        interfaceConnected: setColumn,
        interfaceRemoved: setColumn,

        tare: function() {
          if (dataColumn) {
            model.properties.tareValue = dataColumn.liveValue;
          }
        },

        // User requests collection
        start: function() {
          // NOTE. Due to architecture switch mid-way, the sensorConnectorInterface layer is turning the
          // start request into a promise, and we're turning it back to events. The lower layer
          // could just ditch promises and emit the corresponding events with no harm. (The state
          // machine prevents almost every practical scenario where we'd see an out-of-date
          // startRequestFailure event while in a state that would respond to it.)
          sensorConnectorInterface.requestStart().catch(function() {
            handle('startRequestFailed');
          });
          this.gotoState('starting');
        },

        controlEnabled: function() {
          message = i18n.t("sensor.messages.connected");
        },

        controlDisabled: function() {
          if (sensorConnectorInterface.inControl.clientName) {
            message = i18n.t("sensor.messages.connected_start_sensorconnector", { controlling_client: sensorConnectorInterface.inControl.clientName });
          } else {
            message = i18n.t("sensor.messages.connected_start_sensorconnector_noname");
          }
        },

        sessionChanged: function() {
          sensorConnectorInterface.stopPolling();
          sensorConnectorInterface.startPolling();
          this.gotoState('connecting');
        },

        // Collection was started by a third party
        collectionStarted: function() {
          this.gotoState('started');
        }
      },

      starting: {
        enterState: function() {
          message = i18n.t("sensor.messages.starting_data_collection");
          isStopped = false;
          var self = this;
          this._startTimerId = setTimeout(5000, function() {
            self.gotoState('startRequestFailed');
          });
        },

        leaveState: function() {
          clearTimeout(this._startTimerId);
        },

        startRequestFailed: function() {
          this.gotoState('errorStarting');
        },

        collectionStarted: function() {
          this.gotoState('started');
        }
      },

      errorStarting: {
        enterState: function() {
          message = i18n.t("sensor.messages.error_starting_data_collection");
          isStopped = true;

          notifier.alert(i18n.t("sensor.messages.error_starting_data_collection_alert"), {
            OK: function() {
              $(this).dialog("close");
              handle('dismissErrorStarting');
            }
          });
        },

        collectionStarted: function() {
          this.gotoState('started');
        },

        dismissErrorStarting: function() {
          this.gotoState('connected');
        }
      },

      started: {
        enterState: function() {
          isStopped = false;
          setColumn();
          isPlayable = false;
          if (canControl) {
            message = i18n.t("sensor.messages.collecting_data");
          } else {
            message = i18n.t("sensor.messages.collecting_data_stop_sensorconnector");
          }

          // Check, just in case. Specifically, when errorStopping transitions here, collection
          // might have stopped in the meantime.
          if ( ! sensorConnectorInterface.isCollecting ) {
            this.gotoState('stopped');
          }

          if ( ! dataColumn ) {
            this.gotoState('startedWithNoDataColumn');
          }
        },

        data: handleData,

        controlEnabled: function() {
          message = i18n.t("sensor.messages.collecting_data");
        },

        controlDisabled: function() {
          message = i18n.t("sensor.messages.collecting_data_stop_sensorconnector");
        },

        stop: function() {
          sensorConnectorInterface.requestStop().catch(function() {
            handle('stopRequestFailed');
          });
          this.gotoState('stopping');
        },

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        }
      },

      // This can happen.
      startedWithNoDataColumn: {
        enterState: function() {
          message = i18n.t("sensor.messages.no_data");

          sensorConnectorInterface.requestStop();
          notifier.alert(i18n.t("sensor.messages.no_data_alert"), {
            OK: function() {
              $(this).dialog("close");
            }
          });
        },

        collectionStopped: function() {
          this.gotoState('stoppedWithNoDataColumn');
        }
      },

      stoppedWithNoDataColumn: {
        enterState: function() {
          if (isAllDataReceived()) {
            this.gotoState('connected');
          }
        },

        data: function() {
          if (isAllDataReceived()) {
            this.gotoState('connected');
          }
        }
      },

      stopping: {
        enterState: function() {
          message = i18n.t("sensor.messages.stopping_data_collection");
        },

        data: handleData,

        stopRequestFailed: function() {
          this.gotoState('errorStopping');
        },

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        }
      },

      errorStopping: {
        enterState: function() {
          message = i18n.t("sensor.messages.error_stopping_data_collection");
          notifier.alert(i18n.t("sensor.messages.error_stopping_data_collection_alert"), {
            OK: function() {
              $(this).dialog("close");
              handle('dismissErrorStopping');
            }
          });
        },

        data: handleData,

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        },

        dismissErrorStopping: function() {
          this.gotoState('started');
        }
      },

      // The device reports the stop of data collection before all data can be received.
      collectionStopped: {
        enterState: function() {
          message = i18n.t("sensor.messages.data_collection_stopped");
          if (isAllDataReceived()) {
            this.gotoState('collectionComplete');
          }
        },

        data: function() {
          handleData();
          if (isAllDataReceived()) {
            this.gotoState('collectionComplete');
          }
        }
      },

      collectionComplete: {
        enterState: function() {
          message = i18n.t("sensor.messages.data_collection_complete");
          isStopped = true;
          dispatch.stop();
        },

        reset: function() {
          initializeStateVariables();
          setSensorReadingDescription();
          this.gotoState('connecting');
          dispatch.reset();
        }
      },

      disconnected: {
        enterState: function() {
          message = i18n.t("sensor.messages.disconnected");
          sensorConnectorInterface.stopPolling();
          var self = this;
          setTimeout(function() {
            self.gotoState('notConnected');
          }, 2000);
        }
      }
    });

    // Automatically wrap all event handlers invocations with makeInvalidatingChange so that
    // outputs update from closure variable state automatically.
    function handle(eventName) {
      var args = Array.prototype.slice.call(arguments, 0);

      model.makeInvalidatingChange(function() {
        var handled = stateMachine.handleEvent.apply(stateMachine, args);

        if ( ! handled ) {
          // special handling of any events not handled by the current state:
          if (eventName === 'connectionTimedOut') {
            stateMachine.gotoState('disconnected');
          } else if (eventName === 'sessionChanged') {
            sensorConnectorInterface.stopPolling();
            stateMachine.gotoState('disconnected');
          } else if (eventName === 'statusErrored') {
            stateMachine.gotoState('initialConnectionFailure');
          }
        }
      });
    }

    sensorConnectorInterface = new SensorConnectorInterface();

    // At least for now, dispatch every interface event to the state machine.
    sensorConnectorInterface.on('*', function() {
      var args = Array.prototype.slice.call(arguments, 0);
      handle.apply(null, [this.event].concat(args));
    });

    // Also, handle "live values" every time they are received.
    sensorConnectorInterface.on('statusReceived', function() {
      if (dataColumn) {
        model.makeInvalidatingChange(function() {
          liveSensorValue = dataColumn.liveValue;
        });
      }
    });

    sensorConnectorInterface.on('controlEnabled',  function() {
      canControl = true;
    });

    sensorConnectorInterface.on('controlDisabled', function() {
      canControl = false;
    });

    labModelerMixin = new LabModelerMixin({
      metadata: metadata,
      setters: {},
      unitsDefinition: unitsDefinition,
      initialProperties: initialProperties,
      usePlaybackSupport: false
    });

    propertySupport = labModelerMixin.propertySupport;
    dispatch = labModelerMixin.dispatchSupport;
    dispatch.addEventTypes("tick", "play", "stop", "tickStart", "tickEnd");

    labModelerMixin.mixInto(model);

    initializeStateVariables();

    if (model.properties.useRandomClientId) {
      model.properties.clientId = Math.floor((1 + Math.random()) * 0x100000000).toString(16); // a 9-digit hex string that always starts with 1
    }

    model.addObserver('clientName', function() {
      sensorConnectorInterface.clientName = model.properties.clientName;
    });

    model.defineOutput('time', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.2f'
    }, function() {
      return time;
    });

    model.defineOutput('displayTime', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.1f'
    }, function() {
      return time;
    });

    displayTimePropertyDescription = model.getPropertyDescription('displayTime');

    model.defineOutput('sensorReading', defaultSensorReadingDescription, function() {
      if (rawSensorValue == null) {
        return rawSensorValue;
      }
      return rawSensorValue - model.properties.tareValue;
    });

    /* Need custom implementation of duration options, because we don't use playbackSupport
      (and playbackSupport's implementation is designed for simulation models that have a
      fixed time per tick */

    model.defineOutput('durationOptions', {}, function() {
      return [1, 5, 10, 15, 20, 30, 45, 60];
    });

    model.formatTime = function(time) {
      return  displayTimePropertyDescription.format(time);
    };

    model.defineOutput('actualUseDuration', {}, labModelerMixin.computeActualUseDuration);

    model.defineOutput('actualDuration', {
      label: "Experiment duration",
      unitType: 'time',
      format: 'f'
    }, labModelerMixin.computeActualDuration);

    // Because sensorReading updates are batched and delivered much later than the live sensor value
    // from the sensor status response, we define a separate liveSensorReading output that can be
    // updated every time the status is polled.
    model.defineOutput('liveSensorReading', defaultSensorReadingDescription, function() {
      if (liveSensorValue == null) {
        return liveSensorValue;
      }
      return liveSensorValue - model.properties.tareValue;
    });

    model.defineOutput('sensorName', {
      label: "Sensor Name"
    }, function() {
      return sensorName;
    });

    model.defineOutput('isStopped', {
      label: "Stopped?"
    }, function() {
      return isStopped;
    });

    // TODO. We need a way to make "model-writable" read only properties.
    model.defineOutput('isPlayable', {
      label: "Startable?"
    }, function() {
      return isPlayable;
    });

    model.defineOutput('hasPlayed', {
      label: "Has successfully collected data?"
    }, function() {
      return stepCounter > 0;
    });

    model.defineOutput('canTare', {
      label: "Can set a tare value?"
    }, function() {
      return canTare && isSensorTareable;
    });

    model.defineOutput('canControl', {
      label: "Can remotely start/stop the Sensor Connector?"
    }, function() {
      return canControl;
    });

    model.defineOutput('hasMultipleSensors', {
      label: "Are multiple sensors connected to the Sensor Connector?"
    }, function() {
      return hasMultipleSensors;
    });

    model.defineOutput('needsReload', {
      label: "Needs Reload?"
    }, function() {
      return needsReload;
    });

    model.defineOutput('message', {
      label: "User Message"
    }, function() {
      return message;
    });

    // Clean up state before we go
    // TODO
    model.on('willReset.model', function() {
      sensorConnectorInterface.stopPolling();
      if (sensorConnectorInterface.isCollecting) {
        sensorConnectorInterface.requestStop();
      }
    });

    model.addObserver('actualDuration', updateDisplayTimeRange);
    updateDisplayTimeRange();

    model.updateAllOutputProperties();
    stateMachine.gotoState('notConnected');

    return model;
  };
});

define('models/sensor-connector/view',['require','common/views/numeric-output-view','common/controllers/basic-dialog','models/sensor-common/i18n-sensor-definitions-connector','common/views/view-state'],function(require) {

  var NumericOutputView    = require('common/views/numeric-output-view'),
      BasicDialog          = require('common/controllers/basic-dialog'),
      getSensorDefinitions = require('models/sensor-common/i18n-sensor-definitions-connector'),
      viewState = require('common/views/view-state');

  return function(model, modelUrl, i18n) {

    // TODO use the formatter from the property description. Right now, it automatically adds
    // units to the returned string (which we don't want here).
    var format = d3.format('.2f');
    var sensorReadingView;
    var view;

    var sensorDefinitions = getSensorDefinitions(i18n);

    function setCanTareState() {
      if (model.properties.canTare) {
        viewState.enableView(view.$zeroButton);
      } else {
        viewState.disableView(view.$zeroButton);
      }
    }

    function setCanConnectState() {
      // if (model.properties.canConnect) {
      //   viewState.enableView(view.$connectButton);
      // } else {
      //   viewState.disableView(view.$connectButton);
      // }
    }

    function setHasMultipleSensorsState() {
      if (model.properties.hasMultipleSensors && model.properties.isPlayable) {
        viewState.enableView(view.$selectSensorButton);
      } else {
        viewState.disableView(view.$selectSensorButton);
      }
    }

    function setMessageText() {
      view.$message.html(model.properties.message);
    }

    function setupModelObservers() {
      model.addObserver('canTare', setCanTareState);
      setCanTareState();

      model.addObserver('canConnect', setCanConnectState);
      setCanConnectState();

      model.addObserver('hasMultipleSensors', setHasMultipleSensorsState);
      model.addObserver('isPlayable', setHasMultipleSensorsState);
      setHasMultipleSensorsState();

      model.addObserver('message', setMessageText);
      setMessageText();
    }

    function chooseSensorPopup() {
      var dialog = new BasicDialog({
        width: "60%",
        dialogClass: 'interactive-dialog no-close',
        closeOnEscape: false,
        title: i18n.t("sensor.choose_sensor_title"),
        buttons: {
          OK: function() {
            console.log("OK Clicked");
            $(this).dialog("close");
            // Change the model's selected sensor
            model.setSelectedSensor($(this).find('input:checked').val());
          },
          Cancel: function() {
            console.log("Cancel Clicked");
            $(this).dialog("close");
          }
        }
      }, i18n);
      var content = "",
          label = "",
          sensors = model.connectedSensors(),
          first = true,
          selectedSensor = model.getSelectedSensor(),
          i, checked, sensorDef;
      for (i = 0; i < sensors.length; i++) {
        if (sensors[i].units !== 's') {
          checked = "";
          if (sensors[i].name) {
            sensorDef = { measurementName: sensors[i].name };
          } else {
            sensorDef = sensorDefinitions[sensors[i].units];
            if (!sensorDef) {
              sensorDef = { measurementName: "Unknown" };
            }
          }
          label = sensorDef.measurementName + " (" + sensors[i].units + ")";
          if (selectedSensor == i || (first && selectedSensor == -1)) {
            checked = "checked ";
          };
          content += "<input type='radio' name='selected-sensor-index' value='" + i + "' " + checked + "/>" + label + "<br/>";
          first = false;
        }
      }
      dialog.setContent(content);
      dialog.open();
    }

    return view = {
      $el: $("<div id='model-container' class='container sensor-model-container' />"),
      remoteAddress: null,

      bindModel: function(newModel, newModelUrl) {
        modelUrl = newModelUrl || modelUrl;
        model = newModel || model;

        setupModelObservers();
      },

      getHeightForWidth: function() {
        return "2.8em";
      },

      // called once we're in the DOM
      setup: function() {
        view.$el.empty();
        view.$controlsContainer = $("<div></div>");
        view.$statusContainer = $("<div></div>");
        // view.$addressInput = $("<div class='address-input'><input type='text' name='address-input' placeholder='address of LabQuest2'></input></div>");
        sensorReadingView = new NumericOutputView({
          id: 'sensor-value-view',
          label: i18n.t("sensor.reading"),
          units: model.getPropertyDescription('sensorReading').getUnitAbbreviation()
        });

        // view.$connectButton = $("<div class='interactive-button'><button>Connect</button></div>");
        view.$zeroButton = $("<div class='interactive-button'><button>" + i18n.t("sensor.zero") + "</button></div>");
        view.$selectSensorButton = $("<div class='interactive-button'><button>" + i18n.t("sensor.select_sensor") + "</button></div>");
        view.$message = $("<div class='message'></div>");
        view.$sensorReading = sensorReadingView.render().addClass("horizontal");

        view.$controlsContainer
          // .append(view.$addressInput)
          // .append(view.$connectButton)
          .append(view.$sensorReading)
          .append(view.$zeroButton)
          .append(view.$selectSensorButton);

        view.$statusContainer
          .append(view.$message);

        view.$el.css('zIndex', 4)
          .append(view.$controlsContainer)
          .append(view.$statusContainer);

        view.$controlsContainer.find('div').addClass('component component-spacing');
        view.$statusContainer.find('div').addClass('component component-spacing');

        sensorReadingView.resize();
        setupModelObservers();

        // if (typeof(view.remoteAddress) == "string") {
        //   view.$addressInput.find('input').val(view.remoteAddress);
        // }
        // view.$addressInput.find('input').on('change', function() {
        //   view.remoteAddress = $(this).val();
        // });

        // view.$connectButton.on('click', 'button', function() {
        //   model.connect(view.$addressInput.find('input').val());
        // });
        view.$zeroButton.on('click', 'button', model.tare);
        view.$selectSensorButton.on('click', 'button', chooseSensorPopup);
      },

      resize: function() {
        if (sensorReadingView) {
          sensorReadingView.resize();
        }
      },

      repaint: function() {},

      setFocus: function () {},

      updateUnits: function(units) {
        sensorReadingView.updateUnits(units);
        if (model.properties.liveSensorReading == null) {
          sensorReadingView.hideUnits();
        }
      },

      update: function() {
        if (model.properties.liveSensorReading == null) {
          sensorReadingView.update("");
          sensorReadingView.hideUnits();
        } else {
          sensorReadingView.update(format(model.properties.liveSensorReading));
          sensorReadingView.showUnits();
        }
      }
    };
  };
});

define('models/sensor-connector/scripting-api',[],function () {
  /**
    Define the model-specific Sensor scripting API used by 'action' scripts on interactive elements.

    The universal Interactive scripting API is extended with the properties of the
    object below which will be exposed to the interactive's 'action' scripts as if
    they were local vars. All other names (including all globals, but excluding
    Javascript builtins) will be unavailable in the script context; and scripts
    are run in strict mode so they don't accidentally expose or read globals.

    @param: parent Common Scripting API
  */
  return function SensorScriptingAPI (parent) {
    return {
      /**
       * Reset the model. The interactives controller will emit a 'willResetModel'.
       * The willResetModel observers can ask to wait for asynchronous confirmation before
       * the model is actually reset.
       * Note that the effect would be almost the same like after reload operation. However
       * the mechanism under the hood is completely different. The biggest difference for
       * authoring is that when .resetModel() is called, the "onLoad" script won't be
       * executed again. It may be also faster than reload.
       * @param  {object} options hash of options, supported properties:
       *                         * propertiesToRetain - a list of properties to save before
       *                           the model reset and restore after reset.
       *                         * cause - cause of the reset action.
       */
      resetModel: function resetModel(options) {
        parent.intController.resetModel(options);
      }
    };
  };
});

/*global define */

define('models/sensor-connector/controller',['require','common/controllers/model-controller','./modeler','./view','./scripting-api'],function (require) {
  // Dependencies.
  var ModelController   = require('common/controllers/model-controller'),
      Model             = require('./modeler'),
      ModelContainer    = require('./view'),
      ScriptingAPI      = require('./scripting-api');

  return function (modelUrl, modelOptions, interactiveController) {
    var controller = new ModelController(modelUrl, modelOptions, interactiveController,
      Model, ModelContainer, ScriptingAPI);

    // Note to self: modelController doesn't emit modelLoaded when the model first loads.
    // This was unexpected...

    function setupModelObservers() {
      var model = controller.model;

      model.addObserver('liveSensorReading', function() {
        controller.updateView();
      });

      model.addObserver('needsReload', function() {
        if (model.properties.needsReload) {
          interactiveController.reloadModel();
        }
      });

      model.addPropertyDescriptionObserver('sensorReading', function() {
        var description = model.getPropertyDescription('sensorReading');
        var view = controller.modelContainer;

        view.updateUnits(description.getUnitAbbreviation());
      });
    }

    interactiveController.on('modelLoaded.sensor-connector-model-controller', setupModelObservers);

    interactiveController.on('modelReset.sensor-connector-model-controller', function() {
      controller.model.set('isNewRunInProgress', false);
    });

    interactiveController.on('willResetModel', function() {
      controller.model.set('isNewRunInProgress', true);
      controller.model.willReset();
    });

    return controller;
  };
});

/*global define: false */

define('models/labquest2/metadata',[],function() {

  return {
    mainProperties: {
      type: {
        defaultValue: "labquest2",
        immutable: true
      },
      sensorType: {
        defaultValue: null
      },
      tareValue: {
        defaultValue: 0
      }
    },
    viewOptions: {
      showClock: {
        defaultValue: true,
        propertyChangeInvalidates: false
      },
      controlButtons: {
        defaultValue: "play_reset",
        propertyChangeInvalidates: false
      },
      controlButtonStyle: {
        defaultValue: "video",
        propertyChangeInvalidates: false,
        serialize: false
      }
    }
  };
});

!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define('labquest2-interface',e):"undefined"!=typeof window?window.sensorLabquest2Interface=e():"undefined"!=typeof global?global.sensorLabquest2Interface=e():"undefined"!=typeof self&&(self.sensorLabquest2Interface=e())}(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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.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(require,module,exports){
/*global XDomainRequest */

'use strict';

// datasets[]
//   columns[]
//     id
//     units
//     data[]
//     liveValue
//     requestedValuesTimeStamp
//     receivedValuesTimeStamp

var RSVP = require('rsvp');

var EventEmitter2 = require('eventemitter2').EventEmitter2;
var events = new EventEmitter2({
    wildcard: true
});

var urlPrefix = '';
var TIME_LIMIT_IN_MS = 10000;

var isPolling = false;

var datasets;
var datasetsById;
var columnsById;
var sessionChangedEmitted;
var currentSessionID;

function initializeSession() {
    datasets = [];
    datasetsById = Object.create(null);
    columnsById = Object.create(null);
    sessionChangedEmitted = false;
}

// see http://www.html5rocks.com/en/tutorials/cors/
function createCORSRequest(method, relativeUrl) {
    var url = urlPrefix + relativeUrl;
    var xhr = new XMLHttpRequest();

    if ('withCredentials' in xhr) {
        xhr.open(method, url, true);
        xhr.responseType = 'json';
        xhr.setRequestHeader('Accept', 'application/json');
    } else if (typeof XDomainRequest !== 'undefined') {
        // IE8/9's XMLHttpRequest object doesn't support CORS; instead, you have to use an
        // 'XDomainRequest' object
        xhr = new XDomainRequest();
        // we can't set custom headers in IE9
        // see http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
        xhr.open(method, url);
    } else {
        return null;
    }

    return xhr;
}

var lastStatusTimeStamp = 0;
var isConnected = false;
var isCollecting = false;
var canControl = true;

// called by timeoutTimer
function connectionTimedOut() {
    events.emit('connectionTimedOut');
    isConnected = false;
}

var timeoutTimer = {
    start: function() {
        this.timerId = setTimeout(connectionTimedOut, TIME_LIMIT_IN_MS);
    },

    reset: function() {
        this.stop();
        this.start();
    },

    stop: function() {
        clearTimeout(this.timerId);
    }
};

var statusIntervalId;

function requestStatus() {
    var xhr = createCORSRequest('GET', '/status');
    // TODO set xhr timeout

    if (!xhr) {
        statusErrored();
        return;
    }

    xhr.onerror = statusErrored;
    xhr.onload = statusLoaded;
    xhr.send();
}

function statusErrored() {
    events.emit('statusErrored');
}

function statusLoaded() {
    var response = this.response || JSON.parse(this.responseText);
    if (typeof(response) === "string") { response = JSON.parse(response); }

    if ( ! isPolling ) {
        return;
    }

    if (response.requestTimeStamp < lastStatusTimeStamp) {
        // stale out-of-order response; drop it like we never got it.
        return;
    }

    if ( ! currentSessionID ) {
        currentSessionID = response.sessionID;
        initializeSession();
    } else if (currentSessionID !== response.sessionID) {
        // Session ID changed on us unexpectedly. Client should probably stop polling, start polling.
        if ( ! sessionChangedEmitted) {
            events.emit('sessionChanged');
            sessionChangedEmitted = true;
        }
        return;
    }

    lastStatusTimeStamp = response.requestTimeStamp;

    timeoutTimer.reset();
    processDatasets(response.sets);
    processColumns(response.columns);

    // TODO liveValue

    isConnected = true;

    events.emit('statusReceived');

    if (isCollecting && ! response.collection.isCollecting) {
        isCollecting = false;
        events.emit('collectionStopped');
    } else if (! isCollecting && response.collection.isCollecting) {
        isCollecting = true;
        events.emit('collectionStarted');
    }

    if (canControl && ! response.collection.canControl) {
        canControl = false;
        events.emit('controlDisabled');
    } else if (! canControl && response.collection.canControl) {
        canControl = true;
        events.emit('controlEnabled');
    }
}

// Handle 'datasets' and 'columns' in the response
function processDatasets(sets) {
    Object.keys(sets).forEach(function(setId) {
        if ( ! datasetsById[setId] ) {
            // mind, no datasetAdded is emitted until the second collection because the first
            // dataset always exists
            events.emit('datasetAdded', setId);
            datasetsById[setId] = {
                columns: [],
                id: setId
            };
            datasets.unshift(datasetsById[setId]);
        }
        // Set the columns array length so that it's the correct size if a column was removed
        datasetsById[setId].columns.length = sets[setId].colIDs.length
    });
    // make sure the highest-numbered dataset is always datasets[0]
    datasets.sort(function(d1, d2) { return d2.setId-d1.setId; });
}

function processColumns(cols) {
    // looks familiar
    var eventsToEmit = [];
    Object.keys(cols).forEach(function(colId) {
        var columnFromResponse = cols[colId];
        var dataset = datasetsById[columnFromResponse.setID];
        var column = columnsById[colId];

        if ( ! column ) {
            eventsToEmit.push(['columnAdded',colId]);
            // Remember, the column information can change
            // HOWEVER, assume a column is never removed from one dataset and added to another
            column = columnsById[colId] = {
                id: null,
                units: null,
                receivedValuesTimeStamp: 0,
                requestedValuesTimeStamp: 0,
                liveValueTimeStamp: 0,
                liveValue: null,
                data: []
            };
        } else if (column !== dataset.columns[columnFromResponse.position]) {
            eventsToEmit.push(['columnMoved',colId]);
        }

        dataset.columns[columnFromResponse.position] = column;

        if (column.units !== null && column.units !== columnFromResponse.units) {
            eventsToEmit.push(['columnTypeChanged',colId]);
        }

        column.units = columnFromResponse.units;
        column.id = colId;
        column.liveValue = parseFloat(columnFromResponse.liveValue || 0);
        column.liveValueTimeStamp = columnFromResponse.liveValueTimeStamp;

        if (column.requestedValuesTimeStamp < columnFromResponse.valuesTimeStamp) {
            requestData(colId, columnFromResponse.valuesTimeStamp);
            column.requestedValuesTimeStamp = columnFromResponse.valuesTimeStamp;
        }
    });

    // Find columns that were removed.
    Object.keys(columnsById).forEach(function(colId) {
        if ( ! cols[colId] ) {
            eventsToEmit.push(['columnRemoved', colId]);
            delete columnsById[colId];
        }
    });

    eventsToEmit.forEach(function(arr) {
        events.emit(arr[0], arr[1]);
    });
}

// Request data if status indicates there's more data
function requestData(colId, timeStamp) {
    var xhr = createCORSRequest('GET', '/columns/' + colId);
    // look, we wouldn't have got here if we didn't support CORS
    xhr.send();

    xhr.onload = function() {
        if ( ! isPolling ) {
            return;
        }
        var response = this.response || JSON.parse(this.responseText);
        if (typeof(response) === "string") { response = JSON.parse(response); }
        var values = response.values;
        var column = columnsById[colId];
        if (timeStamp > column.receivedValuesTimeStamp) {
            column.data.length = 0;
            [].push.apply(column.data, values);
            column.receivedValuesTimeStamp = timeStamp;
            events.emit('data', colId);
        }
    };
}

function promisifyRequest(url) {
    return function() {
        return new RSVP.Promise(function(resolve, reject) {
            var xhr = createCORSRequest('GET', url);
            if ( ! xhr ) {
                reject(new Error("This browser does not appear to support Cross-Origin Resource Sharing"));
            }
            xhr.send();

            // Simply emitting errors isn't quite right because there's no way for the consumer
            // to tie the error to the particular start request
            xhr.onerror = function() {
                reject(this);
            };
            xhr.onload = resolve;
        });
    };
}

module.exports = {

    startPolling: function(address) {
        urlPrefix = 'http://' + address;

        requestStatus();
        isPolling = true;
        isConnected = false;
        timeoutTimer.start();
        statusIntervalId = setInterval(requestStatus, 500);
    },

    stopPolling: function() {
        timeoutTimer.stop();
        clearInterval(statusIntervalId);
        currentSessionID = undefined;
        isPolling = false;
    },

    requestStart: promisifyRequest('/control/start'),

    requestStop: promisifyRequest('/control/stop'),

    on: function() {
        events.on.apply(events, arguments);
    },

    off: function() {
        events.off.apply(events, arguments);
    },

    get datasets() {
        return datasets;
    },

    get isConnected() {
        return isPolling && isConnected;
    },

    get isCollecting() {
        return isPolling && isConnected && isCollecting;
    },

    get canControl() {
        return canControl;
    }
};

},{"eventemitter2":3,"rsvp":4}],2:[function(require,module,exports){
// shim for using process in browser

var process = module.exports = {};

process.nextTick = (function () {
    var canSetImmediate = typeof window !== 'undefined'
    && window.setImmediate;
    var canPost = typeof window !== 'undefined'
    && window.postMessage && window.addEventListener
    ;

    if (canSetImmediate) {
        return function (f) { return window.setImmediate(f) };
    }

    if (canPost) {
        var queue = [];
        window.addEventListener('message', function (ev) {
            var source = ev.source;
            if ((source === window || source === null) && ev.data === 'process-tick') {
                ev.stopPropagation();
                if (queue.length > 0) {
                    var fn = queue.shift();
                    fn();
                }
            }
        }, true);

        return function nextTick(fn) {
            queue.push(fn);
            window.postMessage('process-tick', '*');
        };
    }

    return function nextTick(fn) {
        setTimeout(fn, 0);
    };
})();

process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];

process.binding = function (name) {
    throw new Error('process.binding is not supported');
}

// TODO(shtylman)
process.cwd = function () { return '/' };
process.chdir = function (dir) {
    throw new Error('process.chdir is not supported');
};

},{}],3:[function(require,module,exports){
var process=require("__browserify_process");;!function(exports, undefined) {

  var isArray = Array.isArray ? Array.isArray : function _isArray(obj) {
    return Object.prototype.toString.call(obj) === "[object Array]";
  };
  var defaultMaxListeners = 10;

  function init() {
    this._events = {};
    if (this._conf) {
      configure.call(this, this._conf);
    }
  }

  function configure(conf) {
    if (conf) {

      this._conf = conf;

      conf.delimiter && (this.delimiter = conf.delimiter);
      conf.maxListeners && (this._events.maxListeners = conf.maxListeners);
      conf.wildcard && (this.wildcard = conf.wildcard);
      conf.newListener && (this.newListener = conf.newListener);

      if (this.wildcard) {
        this.listenerTree = {};
      }
    }
  }

  function EventEmitter(conf) {
    this._events = {};
    this.newListener = false;
    configure.call(this, conf);
  }

  //
  // Attention, function return type now is array, always !
  // It has zero elements if no any matches found and one or more
  // elements (leafs) if there are matches
  //
  function searchListenerTree(handlers, type, tree, i) {
    if (!tree) {
      return [];
    }
    var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached,
        typeLength = type.length, currentType = type[i], nextType = type[i+1];
    if (i === typeLength && tree._listeners) {
      //
      // If at the end of the event(s) list and the tree has listeners
      // invoke those listeners.
      //
      if (typeof tree._listeners === 'function') {
        handlers && handlers.push(tree._listeners);
        return [tree];
      } else {
        for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) {
          handlers && handlers.push(tree._listeners[leaf]);
        }
        return [tree];
      }
    }

    if ((currentType === '*' || currentType === '**') || tree[currentType]) {
      //
      // If the event emitted is '*' at this part
      // or there is a concrete match at this patch
      //
      if (currentType === '*') {
        for (branch in tree) {
          if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
            listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1));
          }
        }
        return listeners;
      } else if(currentType === '**') {
        endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*'));
        if(endReached && tree._listeners) {
          // The next element has a _listeners, add it to the handlers.
          listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength));
        }

        for (branch in tree) {
          if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
            if(branch === '*' || branch === '**') {
              if(tree[branch]._listeners && !endReached) {
                listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength));
              }
              listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
            } else if(branch === nextType) {
              listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2));
            } else {
              // No match on this one, shift into the tree but not in the type array.
              listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
            }
          }
        }
        return listeners;
      }

      listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1));
    }

    xTree = tree['*'];
    if (xTree) {
      //
      // If the listener tree will allow any match for this part,
      // then recursively explore all branches of the tree
      //
      searchListenerTree(handlers, type, xTree, i+1);
    }

    xxTree = tree['**'];
    if(xxTree) {
      if(i < typeLength) {
        if(xxTree._listeners) {
          // If we have a listener on a '**', it will catch all, so add its handler.
          searchListenerTree(handlers, type, xxTree, typeLength);
        }

        // Build arrays of matching next branches and others.
        for(branch in xxTree) {
          if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) {
            if(branch === nextType) {
              // We know the next element will match, so jump twice.
              searchListenerTree(handlers, type, xxTree[branch], i+2);
            } else if(branch === currentType) {
              // Current node matches, move into the tree.
              searchListenerTree(handlers, type, xxTree[branch], i+1);
            } else {
              isolatedBranch = {};
              isolatedBranch[branch] = xxTree[branch];
              searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1);
            }
          }
        }
      } else if(xxTree._listeners) {
        // We have reached the end and still on a '**'
        searchListenerTree(handlers, type, xxTree, typeLength);
      } else if(xxTree['*'] && xxTree['*']._listeners) {
        searchListenerTree(handlers, type, xxTree['*'], typeLength);
      }
    }

    return listeners;
  }

  function growListenerTree(type, listener) {

    type = typeof type === 'string' ? type.split(this.delimiter) : type.slice();

    //
    // Looks for two consecutive '**', if so, don't add the event at all.
    //
    for(var i = 0, len = type.length; i+1 < len; i++) {
      if(type[i] === '**' && type[i+1] === '**') {
        return;
      }
    }

    var tree = this.listenerTree;
    var name = type.shift();

    while (name) {

      if (!tree[name]) {
        tree[name] = {};
      }

      tree = tree[name];

      if (type.length === 0) {

        if (!tree._listeners) {
          tree._listeners = listener;
        }
        else if(typeof tree._listeners === 'function') {
          tree._listeners = [tree._listeners, listener];
        }
        else if (isArray(tree._listeners)) {

          tree._listeners.push(listener);

          if (!tree._listeners.warned) {

            var m = defaultMaxListeners;

            if (typeof this._events.maxListeners !== 'undefined') {
              m = this._events.maxListeners;
            }

            if (m > 0 && tree._listeners.length > m) {

              tree._listeners.warned = true;
              console.error('(node) warning: possible EventEmitter memory ' +
                            'leak detected. %d listeners added. ' +
                            'Use emitter.setMaxListeners() to increase limit.',
                            tree._listeners.length);
              console.trace();
            }
          }
        }
        return true;
      }
      name = type.shift();
    }
    return true;
  }

  // By default EventEmitters will print a warning if more than
  // 10 listeners are added to it. This is a useful default which
  // helps finding memory leaks.
  //
  // Obviously not all Emitters should be limited to 10. This function allows
  // that to be increased. Set to zero for unlimited.

  EventEmitter.prototype.delimiter = '.';

  EventEmitter.prototype.setMaxListeners = function(n) {
    this._events || init.call(this);
    this._events.maxListeners = n;
    if (!this._conf) this._conf = {};
    this._conf.maxListeners = n;
  };

  EventEmitter.prototype.event = '';

  EventEmitter.prototype.once = function(event, fn) {
    this.many(event, 1, fn);
    return this;
  };

  EventEmitter.prototype.many = function(event, ttl, fn) {
    var self = this;

    if (typeof fn !== 'function') {
      throw new Error('many only accepts instances of Function');
    }

    function listener() {
      if (--ttl === 0) {
        self.off(event, listener);
      }
      fn.apply(this, arguments);
    }

    listener._origin = fn;

    this.on(event, listener);

    return self;
  };

  EventEmitter.prototype.emit = function() {

    this._events || init.call(this);

    var type = arguments[0];

    if (type === 'newListener' && !this.newListener) {
      if (!this._events.newListener) { return false; }
    }

    // Loop through the *_all* functions and invoke them.
    if (this._all) {
      var l = arguments.length;
      var args = new Array(l - 1);
      for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
      for (i = 0, l = this._all.length; i < l; i++) {
        this.event = type;
        this._all[i].apply(this, args);
      }
    }

    // If there is no 'error' event listener then throw.
    if (type === 'error') {

      if (!this._all &&
        !this._events.error &&
        !(this.wildcard && this.listenerTree.error)) {

        if (arguments[1] instanceof Error) {
          throw arguments[1]; // Unhandled 'error' event
        } else {
          throw new Error("Uncaught, unspecified 'error' event.");
        }
        return false;
      }
    }

    var handler;

    if(this.wildcard) {
      handler = [];
      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
      searchListenerTree.call(this, handler, ns, this.listenerTree, 0);
    }
    else {
      handler = this._events[type];
    }

    if (typeof handler === 'function') {
      this.event = type;
      if (arguments.length === 1) {
        handler.call(this);
      }
      else if (arguments.length > 1)
        switch (arguments.length) {
          case 2:
            handler.call(this, arguments[1]);
            break;
          case 3:
            handler.call(this, arguments[1], arguments[2]);
            break;
          // slower
          default:
            var l = arguments.length;
            var args = new Array(l - 1);
            for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
            handler.apply(this, args);
        }
      return true;
    }
    else if (handler) {
      var l = arguments.length;
      var args = new Array(l - 1);
      for (var i = 1; i < l; i++) args[i - 1] = arguments[i];

      var listeners = handler.slice();
      for (var i = 0, l = listeners.length; i < l; i++) {
        this.event = type;
        listeners[i].apply(this, args);
      }
      return (listeners.length > 0) || this._all;
    }
    else {
      return this._all;
    }

  };

  EventEmitter.prototype.on = function(type, listener) {

    if (typeof type === 'function') {
      this.onAny(type);
      return this;
    }

    if (typeof listener !== 'function') {
      throw new Error('on only accepts instances of Function');
    }
    this._events || init.call(this);

    // To avoid recursion in the case that type == "newListeners"! Before
    // adding it to the listeners, first emit "newListeners".
    this.emit('newListener', type, listener);

    if(this.wildcard) {
      growListenerTree.call(this, type, listener);
      return this;
    }

    if (!this._events[type]) {
      // Optimize the case of one listener. Don't need the extra array object.
      this._events[type] = listener;
    }
    else if(typeof this._events[type] === 'function') {
      // Adding the second element, need to change to array.
      this._events[type] = [this._events[type], listener];
    }
    else if (isArray(this._events[type])) {
      // If we've already got an array, just append.
      this._events[type].push(listener);

      // Check for listener leak
      if (!this._events[type].warned) {

        var m = defaultMaxListeners;

        if (typeof this._events.maxListeners !== 'undefined') {
          m = this._events.maxListeners;
        }

        if (m > 0 && this._events[type].length > m) {

          this._events[type].warned = true;
          console.error('(node) warning: possible EventEmitter memory ' +
                        'leak detected. %d listeners added. ' +
                        'Use emitter.setMaxListeners() to increase limit.',
                        this._events[type].length);
          console.trace();
        }
      }
    }
    return this;
  };

  EventEmitter.prototype.onAny = function(fn) {

    if(!this._all) {
      this._all = [];
    }

    if (typeof fn !== 'function') {
      throw new Error('onAny only accepts instances of Function');
    }

    // Add the function to the event listener collection.
    this._all.push(fn);
    return this;
  };

  EventEmitter.prototype.addListener = EventEmitter.prototype.on;

  EventEmitter.prototype.off = function(type, listener) {
    if (typeof listener !== 'function') {
      throw new Error('removeListener only takes instances of Function');
    }

    var handlers,leafs=[];

    if(this.wildcard) {
      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
      leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
    }
    else {
      // does not use listeners(), so no side effect of creating _events[type]
      if (!this._events[type]) return this;
      handlers = this._events[type];
      leafs.push({_listeners:handlers});
    }

    for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
      var leaf = leafs[iLeaf];
      handlers = leaf._listeners;
      if (isArray(handlers)) {

        var position = -1;

        for (var i = 0, length = handlers.length; i < length; i++) {
          if (handlers[i] === listener ||
            (handlers[i].listener && handlers[i].listener === listener) ||
            (handlers[i]._origin && handlers[i]._origin === listener)) {
            position = i;
            break;
          }
        }

        if (position < 0) {
          continue;
        }

        if(this.wildcard) {
          leaf._listeners.splice(position, 1);
        }
        else {
          this._events[type].splice(position, 1);
        }

        if (handlers.length === 0) {
          if(this.wildcard) {
            delete leaf._listeners;
          }
          else {
            delete this._events[type];
          }
        }
        return this;
      }
      else if (handlers === listener ||
        (handlers.listener && handlers.listener === listener) ||
        (handlers._origin && handlers._origin === listener)) {
        if(this.wildcard) {
          delete leaf._listeners;
        }
        else {
          delete this._events[type];
        }
      }
    }

    return this;
  };

  EventEmitter.prototype.offAny = function(fn) {
    var i = 0, l = 0, fns;
    if (fn && this._all && this._all.length > 0) {
      fns = this._all;
      for(i = 0, l = fns.length; i < l; i++) {
        if(fn === fns[i]) {
          fns.splice(i, 1);
          return this;
        }
      }
    } else {
      this._all = [];
    }
    return this;
  };

  EventEmitter.prototype.removeListener = EventEmitter.prototype.off;

  EventEmitter.prototype.removeAllListeners = function(type) {
    if (arguments.length === 0) {
      !this._events || init.call(this);
      return this;
    }

    if(this.wildcard) {
      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
      var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);

      for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
        var leaf = leafs[iLeaf];
        leaf._listeners = null;
      }
    }
    else {
      if (!this._events[type]) return this;
      this._events[type] = null;
    }
    return this;
  };

  EventEmitter.prototype.listeners = function(type) {
    if(this.wildcard) {
      var handlers = [];
      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
      searchListenerTree.call(this, handlers, ns, this.listenerTree, 0);
      return handlers;
    }

    this._events || init.call(this);

    if (!this._events[type]) this._events[type] = [];
    if (!isArray(this._events[type])) {
      this._events[type] = [this._events[type]];
    }
    return this._events[type];
  };

  EventEmitter.prototype.listenersAny = function() {

    if(this._all) {
      return this._all;
    }
    else {
      return [];
    }

  };

  if (typeof define === 'function' && define.amd) {
    define(function() {
      return EventEmitter;
    });
  } else {
    exports.EventEmitter2 = EventEmitter;
  }

}(typeof process !== 'undefined' && typeof process.title !== 'undefined' && typeof exports !== 'undefined' ? exports : window);

},{"__browserify_process":2}],4:[function(require,module,exports){
"use strict";
var Promise = require("./rsvp/promise")["default"];
var EventTarget = require("./rsvp/events")["default"];
var denodeify = require("./rsvp/node")["default"];
var all = require("./rsvp/all")["default"];
var allSettled = require("./rsvp/all-settled")["default"];
var race = require("./rsvp/race")["default"];
var hash = require("./rsvp/hash")["default"];
var hashSettled = require("./rsvp/hash-settled")["default"];
var rethrow = require("./rsvp/rethrow")["default"];
var defer = require("./rsvp/defer")["default"];
var config = require("./rsvp/config").config;
var configure = require("./rsvp/config").configure;
var map = require("./rsvp/map")["default"];
var resolve = require("./rsvp/resolve")["default"];
var reject = require("./rsvp/reject")["default"];
var filter = require("./rsvp/filter")["default"];
var asap = require("./rsvp/asap")["default"];

config.async = asap; // default async is asap;

function async(callback, arg) {
  config.async(callback, arg);
}

function on() {
  config.on.apply(config, arguments);
}

function off() {
  config.off.apply(config, arguments);
}

// Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ === 'object') {
  var callbacks = window.__PROMISE_INSTRUMENTATION__;
  configure('instrument', true);
  for (var eventName in callbacks) {
    if (callbacks.hasOwnProperty(eventName)) {
      on(eventName, callbacks[eventName]);
    }
  }
}

exports.Promise = Promise;
exports.EventTarget = EventTarget;
exports.all = all;
exports.allSettled = allSettled;
exports.race = race;
exports.hash = hash;
exports.hashSettled = hashSettled;
exports.rethrow = rethrow;
exports.defer = defer;
exports.denodeify = denodeify;
exports.configure = configure;
exports.on = on;
exports.off = off;
exports.resolve = resolve;
exports.reject = reject;
exports.async = async;
exports.map = map;
exports.filter = filter;
},{"./rsvp/all":6,"./rsvp/all-settled":5,"./rsvp/asap":7,"./rsvp/config":8,"./rsvp/defer":9,"./rsvp/events":10,"./rsvp/filter":11,"./rsvp/hash":13,"./rsvp/hash-settled":12,"./rsvp/map":15,"./rsvp/node":16,"./rsvp/promise":17,"./rsvp/race":23,"./rsvp/reject":24,"./rsvp/resolve":25,"./rsvp/rethrow":26}],5:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
var isArray = require('./utils').isArray;
var isNonThenable = require('./utils').isNonThenable;
/**
  `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
  a fail-fast method, it waits until all the promises have returned and
  shows you all the results. This is useful if you want to handle multiple
  promises' failure states together as a set.

  Returns a promise that is fulfilled when all the given promises have been
  settled. The return promise is fulfilled with an array of the states of
  the promises passed into the `promises` array argument.

  Each state object will either indicate fulfillment or rejection, and
  provide the corresponding value or reason. The states will take one of
  the following formats:

  ```javascript
  { state: 'fulfilled', value: value }
    or
  { state: 'rejected', reason: reason }
  ```

  Example:

  ```javascript
  var promise1 = RSVP.Promise.resolve(1);
  var promise2 = RSVP.Promise.reject(new Error('2'));
  var promise3 = RSVP.Promise.reject(new Error('3'));
  var promises = [ promise1, promise2, promise3 ];

  RSVP.allSettled(promises).then(function(array){
    // array == [
    //   { state: 'fulfilled', value: 1 },
    //   { state: 'rejected', reason: Error },
    //   { state: 'rejected', reason: Error }
    // ]
    // Note that for the second item, reason.message will be "2", and for the
    // third item, reason.message will be "3".
  }, function(error) {
    // Not run. (This block would only be called if allSettled had failed,
    // for instance if passed an incorrect argument type.)
  });
  ```

  @method allSettled
  @static
  @for RSVP
  @param {Array} promises
  @param {String} label - optional string that describes the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled with an array of the settled
  states of the constituent promises.
*/
exports['default'] = function allSettled(entries, label) {
    return new Promise(function (resolve, reject) {
        if (!isArray(entries)) {
            throw new TypeError('You must pass an array to allSettled.');
        }
        var remaining = entries.length;
        var entry;
        if (remaining === 0) {
            resolve([]);
            return;
        }
        var results = new Array(remaining);
        function fulfilledResolver(index$2) {
            return function (value) {
                resolveAll(index$2, fulfilled(value));
            };
        }
        function rejectedResolver(index$2) {
            return function (reason) {
                resolveAll(index$2, rejected(reason));
            };
        }
        function resolveAll(index$2, value) {
            results[index$2] = value;
            if (--remaining === 0) {
                resolve(results);
            }
        }
        for (var index = 0; index < entries.length; index++) {
            entry = entries[index];
            if (isNonThenable(entry)) {
                resolveAll(index, fulfilled(entry));
            } else {
                Promise.resolve(entry).then(fulfilledResolver(index), rejectedResolver(index));
            }
        }
    }, label);
};
function fulfilled(value) {
    return {
        state: 'fulfilled',
        value: value
    };
}
function rejected(reason) {
    return {
        state: 'rejected',
        reason: reason
    };
}
},{"./promise":17,"./utils":27}],6:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  This is a convenient alias for `RSVP.Promise.all`.

  @method all
  @static
  @for RSVP
  @param {Array} array Array of promises.
  @param {String} label An optional label. This is useful
  for tooling.
*/
exports['default'] = function all(array, label) {
    return Promise.all(array, label);
};
},{"./promise":17}],7:[function(require,module,exports){
var process=require("__browserify_process");'use strict';
exports['default'] = function asap(callback, arg) {
    var length = queue.push([
            callback,
            arg
        ]);
    if (length === 1) {
        // If length is 1, that means that we need to schedule an async flush.
        // If additional callbacks are queued before the queue is flushed, they
        // will be processed by this flush that we are scheduling.
        scheduleFlush();
    }
};
var browserGlobal = typeof window !== 'undefined' ? window : {};
var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
// node
function useNextTick() {
    return function () {
        process.nextTick(flush);
    };
}
function useMutationObserver() {
    var iterations = 0;
    var observer = new BrowserMutationObserver(flush);
    var node = document.createTextNode('');
    observer.observe(node, { characterData: true });
    return function () {
        node.data = iterations = ++iterations % 2;
    };
}
function useSetTimeout() {
    return function () {
        setTimeout(flush, 1);
    };
}
var queue = [];
function flush() {
    for (var i = 0; i < queue.length; i++) {
        var tuple = queue[i];
        var callback = tuple[0], arg = tuple[1];
        callback(arg);
    }
    queue.length = 0;
}
var scheduleFlush;
// Decide what async method to use to triggering processing of queued callbacks:
if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
    scheduleFlush = useNextTick();
} else if (BrowserMutationObserver) {
    scheduleFlush = useMutationObserver();
} else {
    scheduleFlush = useSetTimeout();
}
},{"__browserify_process":2}],8:[function(require,module,exports){
'use strict';
var EventTarget = require('./events')['default'];
var config = { instrument: false };
EventTarget.mixin(config);
function configure(name, value) {
    if (name === 'onerror') {
        // handle for legacy users that expect the actual
        // error to be passed to their function added via
        // `RSVP.configure('onerror', someFunctionHere);`
        config.on('error', value);
        return;
    }
    if (arguments.length === 2) {
        config[name] = value;
    } else {
        return config[name];
    }
}
exports.config = config;
exports.configure = configure;
},{"./events":10}],9:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  `RSVP.defer` returns an object similar to jQuery's `$.Deferred`.
  `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
  interface. New code should use the `RSVP.Promise` constructor instead.

  The object returned from `RSVP.defer` is a plain object with three properties:

  * promise - an `RSVP.Promise`.
  * reject - a function that causes the `promise` property on this object to
    become rejected
  * resolve - a function that causes the `promise` property on this object to
    become fulfilled.

  Example:

   ```javascript
   var deferred = RSVP.defer();

   deferred.resolve("Success!");

   defered.promise.then(function(value){
     // value here is "Success!"
   });
   ```

  @method defer
  @static
  @for RSVP
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @return {Object}
 */
exports['default'] = function defer(label) {
    var deferred = {};
    deferred.promise = new Promise(function (resolve, reject) {
        deferred.resolve = resolve;
        deferred.reject = reject;
    }, label);
    return deferred;
};
},{"./promise":17}],10:[function(require,module,exports){
'use strict';
function indexOf(callbacks, callback) {
    for (var i = 0, l = callbacks.length; i < l; i++) {
        if (callbacks[i] === callback) {
            return i;
        }
    }
    return -1;
}
function callbacksFor(object) {
    var callbacks = object._promiseCallbacks;
    if (!callbacks) {
        callbacks = object._promiseCallbacks = {};
    }
    return callbacks;
}
/**
  @class RSVP.EventTarget
*/
exports['default'] = {
    mixin: function (object) {
        object.on = this.on;
        object.off = this.off;
        object.trigger = this.trigger;
        object._promiseCallbacks = undefined;
        return object;
    },
    on: function (eventName, callback) {
        var allCallbacks = callbacksFor(this), callbacks;
        callbacks = allCallbacks[eventName];
        if (!callbacks) {
            callbacks = allCallbacks[eventName] = [];
        }
        if (indexOf(callbacks, callback) === -1) {
            callbacks.push(callback);
        }
    },
    off: function (eventName, callback) {
        var allCallbacks = callbacksFor(this), callbacks, index;
        if (!callback) {
            allCallbacks[eventName] = [];
            return;
        }
        callbacks = allCallbacks[eventName];
        index = indexOf(callbacks, callback);
        if (index !== -1) {
            callbacks.splice(index, 1);
        }
    },
    trigger: function (eventName, options) {
        var allCallbacks = callbacksFor(this), callbacks, callbackTuple, callback, binding;
        if (callbacks = allCallbacks[eventName]) {
            // Don't cache the callbacks.length since it may grow
            for (var i = 0; i < callbacks.length; i++) {
                callback = callbacks[i];
                callback(options);
            }
        }
    }
};
},{}],11:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
var isFunction = require('./utils').isFunction;
/**
 `RSVP.filter` is similar to JavaScript's native `filter` method, except that it
  waits for all promises to become fulfilled before running the `filterFn` on
  each item in given to `promises`. `RSVP.filter` returns a promise that will
  become fulfilled with the result of running `filterFn` on the values the
  promises become fulfilled with.

  For example:

  ```javascript

  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.resolve(2);
  var promise3 = RSVP.resolve(3);

  var filterFn = function(item){
    return item > 1;
  };

  RSVP.filter(promises, filterFn).then(function(result){
    // result is [ 2, 3 ]
  });
  ```

  If any of the `promises` given to `RSVP.filter` are rejected, the first promise
  that is rejected will be given as an argument to the returned promise's
  rejection handler. For example:

  ```javascript
  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.reject(new Error("2"));
  var promise3 = RSVP.reject(new Error("3"));
  var promises = [ promise1, promise2, promise3 ];

  var filterFn = function(item){
    return item > 1;
  };

  RSVP.filter(promises, filterFn).then(function(array){
    // Code here never runs because there are rejected promises!
  }, function(reason) {
    // reason.message === "2"
  });
  ```

  `RSVP.filter` will also wait for any promises returned from `filterFn`.
  For instance, you may want to fetch a list of users then return a subset
  of those users based on some asynchronous operation:

  ```javascript

  var alice = { name: 'alice' };
  var bob   = { name: 'bob' };
  var users = [ alice, bob ];

  var promises = users.map(function(user){
    return RSVP.resolve(user);
  });

  var filterFn = function(user){
    // Here, Alice has permissions to create a blog post, but Bob does not.
    return getPrivilegesForUser(user).then(function(privs){
      return privs.can_create_blog_post === true;
    });
  };
  RSVP.filter(promises, filterFn).then(function(users){
    // true, because the server told us only Alice can create a blog post.
    users.length === 1;
    // false, because Alice is the only user present in `users`
    users[0] === bob;
  });
  ```

  @method filter
  @static
  @for RSVP
  @param {Array} promises
  @param {Function} filterFn - function to be called on each resolved value to
  filter the final results.
  @param {String} label optional string describing the promise. Useful for
  tooling.
  @return {Promise}
*/
exports['default'] = function filter(promises, filterFn, label) {
    return Promise.all(promises, label).then(function (values) {
        if (!isFunction(filterFn)) {
            throw new TypeError('You must pass a function as filter\'s second argument.');
        }
        var length = values.length;
        var filtered = new Array(length);
        for (var i = 0; i < length; i++) {
            filtered[i] = filterFn(values[i]);
        }
        return Promise.all(filtered, label).then(function (filtered$2) {
            var results = new Array(length);
            var newLength = 0;
            for (var i$2 = 0; i$2 < length; i$2++) {
                if (filtered$2[i$2] === true) {
                    results[newLength] = values[i$2];
                    newLength++;
                }
            }
            results.length = newLength;
            return results;
        });
    });
};
},{"./promise":17,"./utils":27}],12:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
var isNonThenable = require('./utils').isNonThenable;
var keysOf = require('./utils').keysOf;
/**
  `RSVP.hashSettled` is similar to `RSVP.allSettled`, but takes an object
  instead of an array for its `promises` argument.

  Unlike `RSVP.all` or `RSVP.hash`, which implement a fail-fast method,
  but like `RSVP.allSettled`, `hashSettled` waits until all the
  constituent promises have returned and then shows you all the results
  with their states and values/reasons. This is useful if you want to
  handle multiple promises' failure states together as a set.

  Returns a promise that is fulfilled when all the given promises have been
  settled, or rejected if the passed parameters are invalid.

  The returned promise is fulfilled with a hash that has the same key names as
  the `promises` object argument. If any of the values in the object are not
  promises, they will be copied over to the fulfilled object and marked with state
  'fulfilled'.

  Example:

  ```javascript
  var promises = {
    myPromise: RSVP.Promise.resolve(1),
    yourPromise: RSVP.Promise.resolve(2),
    theirPromise: RSVP.Promise.resolve(3),
    notAPromise: 4
  };

  RSVP.hashSettled(promises).then(function(hash){
    // hash here is an object that looks like:
    // {
    //   myPromise: { state: 'fulfilled', value: 1 },
    //   yourPromise: { state: 'fulfilled', value: 2 },
    //   theirPromise: { state: 'fulfilled', value: 3 },
    //   notAPromise: { state: 'fulfilled', value: 4 }
    // }
  });
  ```

  If any of the `promises` given to `RSVP.hash` are rejected, the state will
  be set to 'rejected' and the reason for rejection provided.

  Example:

  ```javascript
  var promises = {
    myPromise: RSVP.Promise.resolve(1),
    rejectedPromise: RSVP.Promise.reject(new Error('rejection')),
    anotherRejectedPromise: RSVP.Promise.reject(new Error('more rejection')),
  };

  RSVP.hashSettled(promises).then(function(hash){
    // hash here is an object that looks like:
    // {
    //   myPromise:              { state: 'fulfilled', value: 1 },
    //   rejectedPromise:        { state: 'rejected', reason: Error },
    //   anotherRejectedPromise: { state: 'rejected', reason: Error },
    // }
    // Note that for rejectedPromise, reason.message == 'rejection',
    // and for anotherRejectedPromise, reason.message == 'more rejection'.
  });
  ```

  An important note: `RSVP.hashSettled` is intended for plain JavaScript objects that
  are just a set of keys and values. `RSVP.hashSettled` will NOT preserve prototype
  chains.

  Example:

  ```javascript
  function MyConstructor(){
    this.example = RSVP.Promise.resolve('Example');
  }

  MyConstructor.prototype = {
    protoProperty: RSVP.Promise.resolve('Proto Property')
  };

  var myObject = new MyConstructor();

  RSVP.hashSettled(myObject).then(function(hash){
    // protoProperty will not be present, instead you will just have an
    // object that looks like:
    // {
    //   example: { state: 'fulfilled', value: 'Example' }
    // }
    //
    // hash.hasOwnProperty('protoProperty'); // false
    // 'undefined' === typeof hash.protoProperty
  });
  ```

  @method hashSettled
  @for RSVP
  @param {Object} promises
  @param {String} label optional string that describes the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled when when all properties of `promises`
  have been settled.
  @static
*/
exports['default'] = function hashSettled(object, label) {
    return new Promise(function (resolve, reject) {
        var results = {};
        var keys = keysOf(object);
        var remaining = keys.length;
        var entry, property;
        if (remaining === 0) {
            resolve(results);
            return;
        }
        function fulfilledResolver(property$2) {
            return function (value) {
                resolveAll(property$2, fulfilled(value));
            };
        }
        function rejectedResolver(property$2) {
            return function (reason) {
                resolveAll(property$2, rejected(reason));
            };
        }
        function resolveAll(property$2, value) {
            results[property$2] = value;
            if (--remaining === 0) {
                resolve(results);
            }
        }
        for (var i = 0; i < keys.length; i++) {
            property = keys[i];
            entry = object[property];
            if (isNonThenable(entry)) {
                resolveAll(property, fulfilled(entry));
            } else {
                Promise.resolve(entry).then(fulfilledResolver(property), rejectedResolver(property));
            }
        }
    });
};
function fulfilled(value) {
    return {
        state: 'fulfilled',
        value: value
    };
}
function rejected(reason) {
    return {
        state: 'rejected',
        reason: reason
    };
}
},{"./promise":17,"./utils":27}],13:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
var isNonThenable = require('./utils').isNonThenable;
var keysOf = require('./utils').keysOf;
/**
  `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array
  for its `promises` argument.

  Returns a promise that is fulfilled when all the given promises have been
  fulfilled, or rejected if any of them become rejected. The returned promise
  is fulfilled with a hash that has the same key names as the `promises` object
  argument. If any of the values in the object are not promises, they will
  simply be copied over to the fulfilled object.

  Example:

  ```javascript
  var promises = {
    myPromise: RSVP.resolve(1),
    yourPromise: RSVP.resolve(2),
    theirPromise: RSVP.resolve(3),
    notAPromise: 4
  };

  RSVP.hash(promises).then(function(hash){
    // hash here is an object that looks like:
    // {
    //   myPromise: 1,
    //   yourPromise: 2,
    //   theirPromise: 3,
    //   notAPromise: 4
    // }
  });
  ````

  If any of the `promises` given to `RSVP.hash` are rejected, the first promise
  that is rejected will be given as the reason to the rejection handler.

  Example:

  ```javascript
  var promises = {
    myPromise: RSVP.resolve(1),
    rejectedPromise: RSVP.reject(new Error("rejectedPromise")),
    anotherRejectedPromise: RSVP.reject(new Error("anotherRejectedPromise")),
  };

  RSVP.hash(promises).then(function(hash){
    // Code here never runs because there are rejected promises!
  }, function(reason) {
    // reason.message === "rejectedPromise"
  });
  ```

  An important note: `RSVP.hash` is intended for plain JavaScript objects that
  are just a set of keys and values. `RSVP.hash` will NOT preserve prototype
  chains.

  Example:

  ```javascript
  function MyConstructor(){
    this.example = RSVP.resolve("Example");
  }

  MyConstructor.prototype = {
    protoProperty: RSVP.resolve("Proto Property")
  };

  var myObject = new MyConstructor();

  RSVP.hash(myObject).then(function(hash){
    // protoProperty will not be present, instead you will just have an
    // object that looks like:
    // {
    //   example: "Example"
    // }
    //
    // hash.hasOwnProperty('protoProperty'); // false
    // 'undefined' === typeof hash.protoProperty
  });
  ```

  @method hash
  @static
  @for RSVP
  @param {Object} promises
  @param {String} label optional string that describes the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled when all properties of `promises`
  have been fulfilled, or rejected if any of them become rejected.
*/
exports['default'] = function hash(object, label) {
    return new Promise(function (resolve, reject) {
        var results = {};
        var keys = keysOf(object);
        var remaining = keys.length;
        var entry, property;
        if (remaining === 0) {
            resolve(results);
            return;
        }
        function fulfilledTo(property$2) {
            return function (value) {
                results[property$2] = value;
                if (--remaining === 0) {
                    resolve(results);
                }
            };
        }
        function onRejection(reason) {
            remaining = 0;
            reject(reason);
        }
        for (var i = 0; i < keys.length; i++) {
            property = keys[i];
            entry = object[property];
            if (isNonThenable(entry)) {
                results[property] = entry;
                if (--remaining === 0) {
                    resolve(results);
                }
            } else {
                Promise.resolve(entry).then(fulfilledTo(property), onRejection);
            }
        }
    });
};
},{"./promise":17,"./utils":27}],14:[function(require,module,exports){
'use strict';
var config = require('./config').config;
var now = require('./utils').now;
exports['default'] = function instrument(eventName, promise, child) {
    // instrumentation should not disrupt normal usage.
    try {
        config.trigger(eventName, {
            guid: promise._guidKey + promise._id,
            eventName: eventName,
            detail: promise._detail,
            childGuid: child && promise._guidKey + child._id,
            label: promise._label,
            timeStamp: now(),
            stack: new Error(promise._label).stack
        });
    } catch (error) {
        setTimeout(function () {
            throw error;
        }, 0);
    }
};
},{"./config":8,"./utils":27}],15:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
var isArray = require('./utils').isArray;
var isFunction = require('./utils').isFunction;
/**
 `RSVP.map` is similar to JavaScript's native `map` method, except that it
  waits for all promises to become fulfilled before running the `mapFn` on
  each item in given to `promises`. `RSVP.map` returns a promise that will
  become fulfilled with the result of running `mapFn` on the values the promises
  become fulfilled with.

  For example:

  ```javascript

  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.resolve(2);
  var promise3 = RSVP.resolve(3);
  var promises = [ promise1, promise2, promise3 ];

  var mapFn = function(item){
    return item + 1;
  };

  RSVP.map(promises, mapFn).then(function(result){
    // result is [ 2, 3, 4 ]
  });
  ```

  If any of the `promises` given to `RSVP.map` are rejected, the first promise
  that is rejected will be given as an argument to the returned promise's
  rejection handler. For example:

  ```javascript
  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.reject(new Error("2"));
  var promise3 = RSVP.reject(new Error("3"));
  var promises = [ promise1, promise2, promise3 ];

  var mapFn = function(item){
    return item + 1;
  };

  RSVP.map(promises, mapFn).then(function(array){
    // Code here never runs because there are rejected promises!
  }, function(reason) {
    // reason.message === "2"
  });
  ```

  `RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
  say you want to get all comments from a set of blog posts, but you need
  the blog posts first becuase they contain a url to those comments.

  ```javscript

  var mapFn = function(blogPost){
    // getComments does some ajax and returns an RSVP.Promise that is fulfilled
    // with some comments data
    return getComments(blogPost.comments_url);
  };

  // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
  // with some blog post data
  RSVP.map(getBlogPosts(), mapFn).then(function(comments){
    // comments is the result of asking the server for the comments
    // of all blog posts returned from getBlogPosts()
  });
  ```

  @method map
  @static
  @for RSVP
  @param {Array} promises
  @param {Function} mapFn function to be called on each fulfilled promise.
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled with the result of calling
  `mapFn` on each fulfilled promise or value when they become fulfilled.
   The promise will be rejected if any of the given `promises` become rejected.
  @static
*/
exports['default'] = function map(promises, mapFn, label) {
    return Promise.all(promises, label).then(function (values) {
        if (!isFunction(mapFn)) {
            throw new TypeError('You must pass a function as map\'s second argument.');
        }
        var length = values.length;
        var results = new Array(length);
        for (var i = 0; i < length; i++) {
            results[i] = mapFn(values[i]);
        }
        return Promise.all(results, label);
    });
};
},{"./promise":17,"./utils":27}],16:[function(require,module,exports){
'use strict';
/* global  arraySlice */
var Promise = require('./promise')['default'];
var isArray = require('./utils').isArray;
/**
  `RSVP.denodeify` takes a "node-style" function and returns a function that
  will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
  browser when you'd prefer to use promises over using callbacks. For example,
  `denodeify` transforms the following:

  ```javascript
  var fs = require('fs');

  fs.readFile('myfile.txt', function(err, data){
    if (err) return handleError(err);
    handleData(data);
  });
  ```

  into:

  ```javascript
  var fs = require('fs');
  var readFile = RSVP.denodeify(fs.readFile);

  readFile('myfile.txt').then(handleData, handleError);
  ```

  If the node function has multiple success parameters, then `denodeify`
  just returns the first one:

  ```javascript
  var request = RSVP.denodeify(require('request'));

  request('http://example.com').then(function(res) {
    // ...
  });
  ```

  However, if you need all success parameters, setting `denodeify`'s
  second parameter to `true` causes it to return all success parameters
  as an array:

  ```javascript
  var request = RSVP.denodeify(require('request'), true);

  request('http://example.com').then(function(result) {
    // result[0] -> res
    // result[1] -> body
  });
  ```

  Or if you pass it an array with names it returns the parameters as a hash:

  ```javascript
  var request = RSVP.denodeify(require('request'), ['res', 'body']);

  request('http://example.com').then(function(result) {
    // result.res
    // result.body
  });
  ```

  Sometimes you need to retain the `this`:

  ```javascript
  var app = require('express')();
  var render = RSVP.denodeify(app.render.bind(app));
  ```

  The denodified function inherits from the original function. It works in all
  environments, except IE 10 and below. Consequently all properties of the original
  function are available to you. However, any properties you change on the
  denodeified function won't be changed on the original function. Example:

  ```javascript
  var request = RSVP.denodeify(require('request')),
      cookieJar = request.jar(); // <- Inheritance is used here

  request('http://example.com', {jar: cookieJar}).then(function(res) {
    // cookieJar.cookies holds now the cookies returned by example.com
  });
  ```

  Using `denodeify` makes it easier to compose asynchronous operations instead
  of using callbacks. For example, instead of:

  ```javascript
  var fs = require('fs');

  fs.readFile('myfile.txt', function(err, data){
    if (err) { ... } // Handle error
    fs.writeFile('myfile2.txt', data, function(err){
      if (err) { ... } // Handle error
      console.log('done')
    });
  });
  ```

  you can chain the operations together using `then` from the returned promise:

  ```javascript
  var fs = require('fs');
  var readFile = RSVP.denodeify(fs.readFile);
  var writeFile = RSVP.denodeify(fs.writeFile);

  readFile('myfile.txt').then(function(data){
    return writeFile('myfile2.txt', data);
  }).then(function(){
    console.log('done')
  }).catch(function(error){
    // Handle error
  });
  ```

  @method denodeify
  @static
  @for RSVP
  @param {Function} nodeFunc a "node-style" function that takes a callback as
  its last argument. The callback expects an error to be passed as its first
  argument (if an error occurred, otherwise null), and the value from the
  operation as its second argument ("function(err, value){ }").
  @param {Boolean|Array} argumentNames An optional paramter that if set
  to `true` causes the promise to fulfill with the callback's success arguments
  as an array. This is useful if the node function has multiple success
  paramters. If you set this paramter to an array with names, the promise will
  fulfill with a hash with these names as keys and the success parameters as
  values.
  @return {Function} a function that wraps `nodeFunc` to return an
  `RSVP.Promise`
  @static
*/
exports['default'] = function denodeify(nodeFunc, argumentNames) {
    var asArray = argumentNames === true;
    var asHash = isArray(argumentNames);
    function denodeifiedFunction() {
        var length = arguments.length;
        var nodeArgs = new Array(length);
        for (var i = 0; i < length; i++) {
            nodeArgs[i] = arguments[i];
        }
        var thisArg;
        if (!asArray && !asHash && argumentNames) {
            console.warn('Deprecation: RSVP.denodeify() doesn\'t allow setting the ' + '"this" binding anymore. Use yourFunction.bind(yourThis) instead.');
            thisArg = argumentNames;
        } else {
            thisArg = this;
        }
        return Promise.all(nodeArgs).then(function (nodeArgs$2) {
            return new Promise(resolver);
            // sweet.js has a bug, this resolver can't be defined in the constructor
            // or the arraySlice macro doesn't work
            function resolver(resolve, reject) {
                function callback() {
                    var length$2 = arguments.length;
                    var args = new Array(length$2);
                    for (var i$2 = 0; i$2 < length$2; i$2++) {
                        args[i$2] = arguments[i$2];
                    }
                    var error = args[0];
                    var value = args[1];
                    if (error) {
                        reject(error);
                    } else if (asArray) {
                        resolve(args.slice(1));
                    } else if (asHash) {
                        var obj = {};
                        var successArguments = args.slice(1);
                        var name;
                        var i$3;
                        for (i$3 = 0; i$3 < argumentNames.length; i$3++) {
                            name = argumentNames[i$3];
                            obj[name] = successArguments[i$3];
                        }
                        resolve(obj);
                    } else {
                        resolve(value);
                    }
                }
                nodeArgs$2.push(callback);
                nodeFunc.apply(thisArg, nodeArgs$2);
            }
        });
    }
    denodeifiedFunction.__proto__ = nodeFunc;
    return denodeifiedFunction;
};
},{"./promise":17,"./utils":27}],17:[function(require,module,exports){
'use strict';
var config = require('./config').config;
var EventTarget = require('./events')['default'];
var instrument = require('./instrument')['default'];
var objectOrFunction = require('./utils').objectOrFunction;
var isFunction = require('./utils').isFunction;
var now = require('./utils').now;
var cast = require('./promise/cast')['default'];
var all = require('./promise/all')['default'];
var race = require('./promise/race')['default'];
var Resolve = require('./promise/resolve')['default'];
var Reject = require('./promise/reject')['default'];
var guidKey = 'rsvp_' + now() + '-';
var counter = 0;
function noop() {
}
exports['default'] = Promise;
/**
  Promise objects represent the eventual result of an asynchronous operation. The
  primary way of interacting with a promise is through its `then` method, which
  registers callbacks to receive either a promise’s eventual value or the reason
  why the promise cannot be fulfilled.

  Terminology
  -----------

  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
  - `thenable` is an object or function that defines a `then` method.
  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
  - `exception` is a value that is thrown using the throw statement.
  - `reason` is a value that indicates why a promise was rejected.
  - `settled` the final resting state of a promise, fulfilled or rejected.

  A promise can be in one of three states: pending, fulfilled, or rejected.

  Promises that are fulfilled have a fulfillment value and are in the fulfilled
  state.  Promises that are rejected have a rejection reason and are in the
  rejected state.  A fulfillment value is never a thenable.

  Promises can also be said to *resolve* a value.  If this value is also a
  promise, then the original promise's settled state will match the value's
  settled state.  So a promise that *resolves* a promise that rejects will
  itself reject, and a promise that *resolves* a promise that fulfills will
  itself fulfill.


  Basic Usage:
  ------------

  ```js
  var promise = new Promise(function(resolve, reject) {
    // on success
    resolve(value);

    // on failure
    reject(reason);
  });

  promise.then(function(value) {
    // on fulfillment
  }, function(reason) {
    // on rejection
  });
  ```

  Advanced Usage:
  ---------------

  Promises shine when abstracting away asynchronous interactions such as
  `XMLHttpRequest`s.

  ```js
  function getJSON(url) {
    return new Promise(function(resolve, reject){
      var xhr = new XMLHttpRequest();

      xhr.open('GET', url);
      xhr.onreadystatechange = handler;
      xhr.responseType = 'json';
      xhr.setRequestHeader('Accept', 'application/json');
      xhr.send();

      function handler() {
        if (this.readyState === this.DONE) {
          if (this.status === 200) {
            resolve(this.response);
          } else {
            reject(new Error("getJSON: `" + url + "` failed with status: [" + this.status + "]");
          }
        }
      };
    });
  }

  getJSON('/posts.json').then(function(json) {
    // on fulfillment
  }, function(reason) {
    // on rejection
  });
  ```

  Unlike callbacks, promises are great composable primitives.

  ```js
  Promise.all([
    getJSON('/posts'),
    getJSON('/comments')
  ]).then(function(values){
    values[0] // => postsJSON
    values[1] // => commentsJSON

    return values;
  });
  ```

  @class RSVP.Promise
  @param {function}
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @constructor
*/
function Promise(resolver, label) {
    if (!isFunction(resolver)) {
        throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
    }
    if (!(this instanceof Promise)) {
        throw new TypeError('Failed to construct \'Promise\': Please use the \'new\' operator, this object constructor cannot be called as a function.');
    }
    this._id = counter++;
    this._label = label;
    this._subscribers = [];
    if (config.instrument) {
        instrument('created', this);
    }
    if (noop !== resolver) {
        invokeResolver(resolver, this);
    }
}
function invokeResolver(resolver, promise) {
    function resolvePromise(value) {
        resolve(promise, value);
    }
    function rejectPromise(reason) {
        reject(promise, reason);
    }
    try {
        resolver(resolvePromise, rejectPromise);
    } catch (e) {
        rejectPromise(e);
    }
}
Promise.cast = cast;
Promise.all = all;
Promise.race = race;
Promise.resolve = Resolve;
Promise.reject = Reject;
var PENDING = void 0;
var SEALED = 0;
var FULFILLED = 1;
var REJECTED = 2;
function subscribe(parent, child, onFulfillment, onRejection) {
    var subscribers = parent._subscribers;
    var length = subscribers.length;
    subscribers[length] = child;
    subscribers[length + FULFILLED] = onFulfillment;
    subscribers[length + REJECTED] = onRejection;
}
function publish(promise, settled) {
    var child, callback, subscribers = promise._subscribers, detail = promise._detail;
    if (config.instrument) {
        instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
    }
    for (var i = 0; i < subscribers.length; i += 3) {
        child = subscribers[i];
        callback = subscribers[i + settled];
        invokeCallback(settled, child, callback, detail);
    }
    promise._subscribers = null;
}
Promise.prototype = {
    constructor: Promise,
    _id: undefined,
    _guidKey: guidKey,
    _label: undefined,
    _state: undefined,
    _detail: undefined,
    _subscribers: undefined,
    _onerror: function (reason) {
        config.trigger('error', reason);
    },
    then: function (onFulfillment, onRejection, label) {
        var promise = this;
        this._onerror = null;
        var thenPromise = new this.constructor(noop, label);
        if (this._state) {
            var callbacks = arguments;
            config.async(function invokePromiseCallback() {
                invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
            });
        } else {
            subscribe(this, thenPromise, onFulfillment, onRejection);
        }
        if (config.instrument) {
            instrument('chained', promise, thenPromise);
        }
        return thenPromise;
    },
    'catch': function (onRejection, label) {
        return this.then(null, onRejection, label);
    },
    'finally': function (callback, label) {
        var constructor = this.constructor;
        return this.then(function (value) {
            return constructor.resolve(callback()).then(function () {
                return value;
            });
        }, function (reason) {
            return constructor.resolve(callback()).then(function () {
                throw reason;
            });
        }, label);
    }
};
function invokeCallback(settled, promise, callback, detail) {
    var hasCallback = isFunction(callback), value, error, succeeded, failed;
    if (hasCallback) {
        try {
            value = callback(detail);
            succeeded = true;
        } catch (e) {
            failed = true;
            error = e;
        }
    } else {
        value = detail;
        succeeded = true;
    }
    if (handleThenable(promise, value)) {
        return;
    } else if (hasCallback && succeeded) {
        resolve(promise, value);
    } else if (failed) {
        reject(promise, error);
    } else if (settled === FULFILLED) {
        resolve(promise, value);
    } else if (settled === REJECTED) {
        reject(promise, value);
    }
}
function handleThenable(promise, value) {
    var then = null, resolved;
    try {
        if (promise === value) {
            throw new TypeError('A promises callback cannot return that same promise.');
        }
        if (objectOrFunction(value)) {
            then = value.then;
            if (isFunction(then)) {
                then.call(value, function (val) {
                    if (resolved) {
                        return true;
                    }
                    resolved = true;
                    if (value !== val) {
                        resolve(promise, val);
                    } else {
                        fulfill(promise, val);
                    }
                }, function (val) {
                    if (resolved) {
                        return true;
                    }
                    resolved = true;
                    reject(promise, val);
                }, 'Settle: ' + (promise._label || ' unknown promise'));
                return true;
            }
        }
    } catch (error) {
        if (resolved) {
            return true;
        }
        reject(promise, error);
        return true;
    }
    return false;
}
function resolve(promise, value) {
    if (promise === value) {
        fulfill(promise, value);
    } else if (!handleThenable(promise, value)) {
        fulfill(promise, value);
    }
}
function fulfill(promise, value) {
    if (promise._state !== PENDING) {
        return;
    }
    promise._state = SEALED;
    promise._detail = value;
    config.async(publishFulfillment, promise);
}
function reject(promise, reason) {
    if (promise._state !== PENDING) {
        return;
    }
    promise._state = SEALED;
    promise._detail = reason;
    config.async(publishRejection, promise);
}
function publishFulfillment(promise) {
    publish(promise, promise._state = FULFILLED);
}
function publishRejection(promise) {
    if (promise._onerror) {
        promise._onerror(promise._detail);
    }
    publish(promise, promise._state = REJECTED);
}
},{"./config":8,"./events":10,"./instrument":14,"./promise/all":18,"./promise/cast":19,"./promise/race":20,"./promise/reject":21,"./promise/resolve":22,"./utils":27}],18:[function(require,module,exports){
'use strict';
var isArray = require('../utils').isArray;
var isNonThenable = require('../utils').isNonThenable;
/**
  `RSVP.Promise.all` accepts an array of promises, and returns a new promise which
  is fulfilled with an array of fulfillment values for the passed promises, or
  rejected with the reason of the first passed promise to be rejected. It casts all
  elements of the passed iterable to promises as it runs this algorithm.

  Example:

  ```javascript
  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.resolve(2);
  var promise3 = RSVP.resolve(3);
  var promises = [ promise1, promise2, promise3 ];

  RSVP.Promise.all(promises).then(function(array){
    // The array here would be [ 1, 2, 3 ];
  });
  ```

  If any of the `promises` given to `RSVP.all` are rejected, the first promise
  that is rejected will be given as an argument to the returned promises's
  rejection handler. For example:

  Example:

  ```javascript
  var promise1 = RSVP.resolve(1);
  var promise2 = RSVP.reject(new Error("2"));
  var promise3 = RSVP.reject(new Error("3"));
  var promises = [ promise1, promise2, promise3 ];

  RSVP.Promise.all(promises).then(function(array){
    // Code here never runs because there are rejected promises!
  }, function(error) {
    // error.message === "2"
  });
  ```

  @method all
  @static
  @param {Array} entries array of promises
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled when all `promises` have been
  fulfilled, or rejected if any of them become rejected.
  @static
*/
exports['default'] = function all(entries, label) {
    /*jshint validthis:true */
    var Constructor = this;
    return new Constructor(function (resolve, reject) {
        if (!isArray(entries)) {
            throw new TypeError('You must pass an array to all.');
        }
        var remaining = entries.length;
        var results = new Array(remaining);
        var entry, pending = true;
        if (remaining === 0) {
            resolve(results);
            return;
        }
        function fulfillmentAt(index$2) {
            return function (value) {
                results[index$2] = value;
                if (--remaining === 0) {
                    resolve(results);
                }
            };
        }
        function onRejection(reason) {
            remaining = 0;
            reject(reason);
        }
        for (var index = 0; index < entries.length; index++) {
            entry = entries[index];
            if (isNonThenable(entry)) {
                results[index] = entry;
                if (--remaining === 0) {
                    resolve(results);
                }
            } else {
                Constructor.resolve(entry).then(fulfillmentAt(index), onRejection);
            }
        }
    }, label);
};
},{"../utils":27}],19:[function(require,module,exports){
'use strict';
/**
  @deprecated

  `RSVP.Promise.cast` coerces its argument to a promise, or returns the
  argument if it is already a promise which shares a constructor with the caster.

  Example:

  ```javascript
  var promise = RSVP.Promise.resolve(1);
  var casted = RSVP.Promise.cast(promise);

  console.log(promise === casted); // true
  ```

  In the case of a promise whose constructor does not match, it is assimilated.
  The resulting promise will fulfill or reject based on the outcome of the
  promise being casted.

  Example:

  ```javascript
  var thennable = $.getJSON('/api/foo');
  var casted = RSVP.Promise.cast(thennable);

  console.log(thennable === casted); // false
  console.log(casted instanceof RSVP.Promise) // true

  casted.then(function(data) {
    // data is the value getJSON fulfills with
  });
  ```

  In the case of a non-promise, a promise which will fulfill with that value is
  returned.

  Example:

  ```javascript
  var value = 1; // could be a number, boolean, string, undefined...
  var casted = RSVP.Promise.cast(value);

  console.log(value === casted); // false
  console.log(casted instanceof RSVP.Promise) // true

  casted.then(function(val) {
    val === value // => true
  });
  ```

  `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the
  following ways:

  * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you
  have something that could either be a promise or a value. RSVP.resolve
  will have the same effect but will create a new promise wrapper if the
  argument is a promise.
  * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to
  promises of the exact class specified, so that the resulting object's `then` is
  ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise).

  @method cast
  @static
  @param {Object} object to be casted
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @return {Promise} promise
*/
exports['default'] = function cast(object, label) {
    /*jshint validthis:true */
    var Constructor = this;
    if (object && typeof object === 'object' && object.constructor === Constructor) {
        return object;
    }
    return new Constructor(function (resolve) {
        resolve(object);
    }, label);
};
},{}],20:[function(require,module,exports){
'use strict';
/* global toString */
var isArray = require('../utils').isArray;
var isFunction = require('../utils').isFunction;
var isNonThenable = require('../utils').isNonThenable;
/**
  `RSVP.Promise.race` returns a new promise which is settled in the same way as the
  first passed promise to settle.

  Example:

  ```javascript
  var promise1 = new RSVP.Promise(function(resolve, reject){
    setTimeout(function(){
      resolve("promise 1");
    }, 200);
  });

  var promise2 = new RSVP.Promise(function(resolve, reject){
    setTimeout(function(){
      resolve("promise 2");
    }, 100);
  });

  RSVP.Promise.race([promise1, promise2]).then(function(result){
    // result === "promise 2" because it was resolved before promise1
    // was resolved.
  });
  ```

  `RSVP.Promise.race` is deterministic in that only the state of the first
  settled promise matters. For example, even if other promises given to the
  `promises` array argument are resolved, but the first settled promise has
  become rejected before the other promises became fulfilled, the returned
  promise will become rejected:

  ```javascript
  var promise1 = new RSVP.Promise(function(resolve, reject){
    setTimeout(function(){
      resolve("promise 1");
    }, 200);
  });

  var promise2 = new RSVP.Promise(function(resolve, reject){
    setTimeout(function(){
      reject(new Error("promise 2"));
    }, 100);
  });

  RSVP.Promise.race([promise1, promise2]).then(function(result){
    // Code here never runs
  }, function(reason){
    // reason.message === "promise2" because promise 2 became rejected before
    // promise 1 became fulfilled
  });
  ```

  An example real-world use case is implementing timeouts:

  ```javascript
  RSVP.Promise.race([ajax('foo.json'), timeout(5000)])
  ```

  @method race
  @static
  @param {Array} promises array of promises to observe
  @param {String} label optional string for describing the promise returned.
  Useful for tooling.
  @return {Promise} a promise which settles in the same way as the first passed
  promise to settle.
*/
exports['default'] = function race(entries, label) {
    /*jshint validthis:true */
    var Constructor = this, entry;
    return new Constructor(function (resolve, reject) {
        if (!isArray(entries)) {
            throw new TypeError('You must pass an array to race.');
        }
        var pending = true;
        function onFulfillment(value) {
            if (pending) {
                pending = false;
                resolve(value);
            }
        }
        function onRejection(reason) {
            if (pending) {
                pending = false;
                reject(reason);
            }
        }
        for (var i = 0; i < entries.length; i++) {
            entry = entries[i];
            if (isNonThenable(entry)) {
                pending = false;
                resolve(entry);
                return;
            } else {
                Constructor.resolve(entry).then(onFulfillment, onRejection);
            }
        }
    }, label);
};
},{"../utils":27}],21:[function(require,module,exports){
'use strict';
/**
  `RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
  It is shorthand for the following:

  ```javascript
  var promise = new RSVP.Promise(function(resolve, reject){
    reject(new Error('WHOOPS'));
  });

  promise.then(function(value){
    // Code here doesn't run because the promise is rejected!
  }, function(reason){
    // reason.message === 'WHOOPS'
  });
  ```

  Instead of writing the above, your code now simply becomes the following:

  ```javascript
  var promise = RSVP.Promise.reject(new Error('WHOOPS'));

  promise.then(function(value){
    // Code here doesn't run because the promise is rejected!
  }, function(reason){
    // reason.message === 'WHOOPS'
  });
  ```

  @method reject
  @static
  @param {Any} reason value that the returned promise will be rejected with.
  @param {String} label optional string for identifying the returned promise.
  Useful for tooling.
  @return {Promise} a promise rejected with the given `reason`.
*/
exports['default'] = function reject(reason, label) {
    /*jshint validthis:true */
    var Constructor = this;
    return new Constructor(function (resolve, reject$2) {
        reject$2(reason);
    }, label);
};
},{}],22:[function(require,module,exports){
'use strict';
/**
  `RSVP.Promise.resolve` returns a promise that will become resolved with the
  passed `value`. It is shorthand for the following:

  ```javascript
  var promise = new RSVP.Promise(function(resolve, reject){
    resolve(1);
  });

  promise.then(function(value){
    // value === 1
  });
  ```

  Instead of writing the above, your code now simply becomes the following:

  ```javascript
  var promise = RSVP.Promise.resolve(1);

  promise.then(function(value){
    // value === 1
  });
  ```

  @method resolve
  @static
  @param {Any} value value that the returned promise will be resolved with
  @param {String} label optional string for identifying the returned promise.
  Useful for tooling.
  @return {Promise} a promise that will become fulfilled with the given
  `value`
*/
exports['default'] = function resolve(object, label) {
    /*jshint validthis:true */
    var Constructor = this;
    if (object && typeof object === 'object' && object.constructor === Constructor) {
        return object;
    }
    return new Constructor(function (resolve$2) {
        resolve$2(object);
    }, label);
};
},{}],23:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  This is a convenient alias for `RSVP.Promise.race`.

  @method race
  @static
  @for RSVP
  @param {Array} array Array of promises.
  @param {String} label An optional label. This is useful
  for tooling.
 */
exports['default'] = function race(array, label) {
    return Promise.race(array, label);
};
},{"./promise":17}],24:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  This is a convenient alias for `RSVP.Promise.reject`.

  @method reject
  @static
  @for RSVP
  @param {Any} reason value that the returned promise will be rejected with.
  @param {String} label optional string for identifying the returned promise.
  Useful for tooling.
  @return {Promise} a promise rejected with the given `reason`.
*/
exports['default'] = function reject(reason, label) {
    return Promise.reject(reason, label);
};
},{"./promise":17}],25:[function(require,module,exports){
'use strict';
var Promise = require('./promise')['default'];
/**
  This is a convenient alias for `RSVP.Promise.resolve`.

  @method resolve
  @static
  @for RSVP
  @param {Any} value value that the returned promise will be resolved with
  @param {String} label optional string for identifying the returned promise.
  Useful for tooling.
  @return {Promise} a promise that will become fulfilled with the given
  `value`
*/
exports['default'] = function resolve(value, label) {
    return Promise.resolve(value, label);
};
},{"./promise":17}],26:[function(require,module,exports){
'use strict';
/**
  `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event
  loop in order to aid debugging.

  Promises A+ specifies that any exceptions that occur with a promise must be
  caught by the promises implementation and bubbled to the last handler. For
  this reason, it is recommended that you always specify a second rejection
  handler function to `then`. However, `RSVP.rethrow` will throw the exception
  outside of the promise, so it bubbles up to your console if in the browser,
  or domain/cause uncaught exception in Node. `rethrow` will also throw the
  error again so the error can be handled by the promise per the spec.

  ```javascript
  function throws(){
    throw new Error('Whoops!');
  }

  var promise = new RSVP.Promise(function(resolve, reject){
    throws();
  });

  promise.catch(RSVP.rethrow).then(function(){
    // Code here doesn't run because the promise became rejected due to an
    // error!
  }, function (err){
    // handle the error here
  });
  ```

  The 'Whoops' error will be thrown on the next turn of the event loop
  and you can watch for it in your console. You can also handle it using a
  rejection handler given to `.then` or `.catch` on the returned promise.

  @method rethrow
  @static
  @for RSVP
  @param {Error} reason reason the promise became rejected.
  @throws Error
  @static
*/
exports['default'] = function rethrow(reason) {
    setTimeout(function () {
        throw reason;
    });
    throw reason;
};
},{}],27:[function(require,module,exports){
'use strict';
function objectOrFunction(x) {
    return typeof x === 'function' || typeof x === 'object' && x !== null;
}
exports.objectOrFunction = objectOrFunction;
function isFunction(x) {
    return typeof x === 'function';
}
exports.isFunction = isFunction;
function isNonThenable(x) {
    return !objectOrFunction(x);
}
exports.isNonThenable = isNonThenable;
var _isArray;
if (!Array.isArray) {
    _isArray = function (x) {
        return Object.prototype.toString.call(x) === '[object Array]';
    };
} else {
    _isArray = Array.isArray;
}
var isArray = _isArray;
exports.isArray = isArray;
// Date.now is not available in browsers < IE9
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
var now = Date.now || function () {
        return new Date().getTime();
    };
exports.now = now;
var keysOf = Object.keys || function (object) {
        var result = [];
        for (var prop in object) {
            result.push(prop);
        }
        return result;
    };
exports.keysOf = keysOf;
},{}]},{},[1])
(1)
});
;
define('models/labquest2/units-definition',[],function() {
  return {
    units: {
      time: {
        name: "second",
        pluralName: "seconds",
        symbol: "s"
      },
      distance: {
        name: "meter",
        pluralName: "meters",
        symbol: "m"
      },
      temperature: {
        name: "degree Celsius",
        pluaralName: "degrees Celsius",
        symbol: "°C"
      },
      light: {
        name: "lux",
        pluralName: "lux",
        symbol: "lux"
      },
      force: {
        name: "Newton",
        pluralName: "Newtons",
        symbol: "N"
      },
      ph: {
        name: "pH Unit",
        pluralName: "pH Units",
        symbol: "pH"
      },
      co2: {
        name: "part per million",
        pluralName: "parts per million",
        symbol: "ppm"
      },
      o2: {
        name: "part per million",
        pluralName: "parts per million",
        symbol: "ppm"
      }
    }
  };
});

/*global define: false */

define('models/labquest2/modeler',['require','common/lab-modeler-mixin','common/property-description','./metadata','common/state-machine','labquest2-interface','./units-definition','models/sensor-common/i18n-sensor-definitions-connector','models/sensor-common/notifier','underscore'],function(require) {

  var LabModelerMixin      = require('common/lab-modeler-mixin'),
      PropertyDescription  = require('common/property-description'),
      metadata             = require('./metadata'),
      StateMachine         = require('common/state-machine'),
      labquest2Interface   = require('labquest2-interface'),
      unitsDefinition      = require('./units-definition'),
      getSensorDefinitions = require('models/sensor-common/i18n-sensor-definitions-connector'),
      Notifier             = require('models/sensor-common/notifier'),
      _ = require('underscore');

  return function Model(initialProperties, opt) {
    var i18n = opt.i18n,
        notifier = new Notifier(i18n),

        labModelerMixin,
        propertySupport,
        dispatch,
        stateMachine,
        timeColumn,
        dataColumn,
        selectedSensor,
        sensorName,
        isStopped,
        needsReload,
        time,
        rawSensorValue,
        liveSensorValue,
        lastLiveValueTimestamp,
        liveValueLastChanged,
        stepCounter,
        isPlayable,
        canConnect,
        canTare,
        liveMode,
        canControl,
        hasMultipleSensors,
        isSensorTareable,
        message,
        model;

    var defaultSensorReadingDescription = {
      label: i18n.t("sensor.measurements.sensor_reading"),
      unitAbbreviation: "-",
      format: '.2f',
      min: 0,
      max: 10
    };
    var sensorDefinitions = getSensorDefinitions(i18n);

    function setSensorReadingDescription() {
      var sensorDefinition;
      var description;

      if (dataColumn) {
        sensorDefinition = sensorDefinitions[dataColumn.units];

        if (sensorDefinition) {
          description = {
            label: sensorDefinition.measurementName,
            format: '.2f',
            min: sensorDefinition.minReading,
            max: sensorDefinition.maxReading
          };
          if (unitsDefinition.units[sensorDefinition.measurementType]) {
            description.unitType = sensorDefinition.measurementType;
          } else {
            description.unitAbbreviation = dataColumn.units;
          }
          isSensorTareable = sensorDefinition.tareable;
          sensorName = sensorDefinition.sensorName;
        } else {
          description = {
            label: i18n.t("sensor.measurements.sensor_reading"),
            unitAbbreviation: dataColumn.units,
            format: '.2f',
            min: 0,
            max: 10
          };
          isSensorTareable = true;
          sensorName = dataColumn.units + " sensor";
        }
      } else {
        description = defaultSensorReadingDescription;
        isSensorTareable = false;
        sensorName = "(no sensor)";
      }

      propertySupport.setPropertyDescription('sensorReading',
        new PropertyDescription(unitsDefinition, description));
      propertySupport.setPropertyDescription('liveSensorReading',
        new PropertyDescription(unitsDefinition, description));
    }

    function initializeStateVariables() {
      isStopped = true;
      canConnect = false;
      canControl = labquest2Interface.canControl;
      hasMultipleSensors = false;
      // Set selectedSensor if it hasn't been set yet
      if (typeof(selectedSensor) === "undefined" || selectedSensor === null) {
        selectedSensor = { index: -1 };
      }
      stepCounter = 0;
      time = 0;
      rawSensorValue = undefined;
      liveSensorValue = undefined;
      lastLiveValueTimestamp = 0;
      liveValueLastChanged = 0;
      timeColumn = undefined;
      dataColumn = undefined;
    }

    function checkColumnAgainstSelected(dataset, idx) {
        var colCandidate = dataset.columns[idx];
        if (colCandidate && colCandidate.units === selectedSensor.units) {
          selectedSensor.index = idx;
          return true;
        }
        return false;
    }

    function setColumn() {
      var dataset = labquest2Interface.datasets[0];
      var newDataColumn,sIdx,colCandidate;

      hasMultipleSensors = dataset.columns.length > 2;

      timeColumn = _.find(dataset.columns, function(column) {
        return column.units === 's';
      });

      // TODO When we want to support multiple sensors, this will have to change.
      // Select the column chosen by the user
      sIdx = selectedSensor.index;
      if (sIdx == -1) {
        newDataColumn = _.find(dataset.columns, function(column, idx) {
          if (column.units !== 's') {
            selectedSensor.index = idx;
            selectedSensor.units = column.units;
            sIdx = idx;
            return true;
          }
          return false;
        });
      }
      if (sIdx >= dataset.columns.length && dataset.columns.length > 1) {
        // we seem to be pointing past the number of columns there are. reset to that last column.
        sIdx = dataset.columns.length - 1;

      }
      newDataColumn = dataset.columns[sIdx];
      if (newDataColumn && selectedSensor.units && newDataColumn.units !== selectedSensor.units) {
        // our selected column seems to have changed out from under us.
        // If a sensor was added to the device, it could be one column higher
        if (checkColumnAgainstSelected(dataset, sIdx+1)) {
          newDataColumn = dataset.columns[sIdx+1];
        } else if (sIdx > 1 && checkColumnAgainstSelected(dataset, sIdx-1)) {
          // it wasn't the one after. let's check the one before.
          newDataColumn = dataset.columns[sIdx-1];
        } else {
          // it seems to be none of them. Reset the selected sensor to the first one.
          newDataColumn = _.find(dataset.columns, function(column, idx) {
            if (column.units !== 's') {
              selectedSensor.index = idx;
              selectedSensor.units = column.units;
              sIdx = idx;
              return true;
            }
            return false;
          });
        }
      }

      dataColumn = newDataColumn;
      if (dataColumn) {
        selectedSensor.units = dataColumn.units;
      }
      setSensorReadingDescription();

      if ( ! dataColumn ) {
        liveSensorValue = undefined;
      }
    }

    function handleData() {
      if (!timeColumn || !dataColumn) {
        return;
      }

      var numberOfValues = Math.min(timeColumn.data.length, dataColumn.data.length);
      for (; stepCounter < numberOfValues; stepCounter++) {
        time = timeColumn.data[stepCounter];
        rawSensorValue = dataColumn.data[stepCounter];
        model.updateAllOutputProperties();
        dispatch.tick();
      }
    }

    function isAllColumnDataReceieved(column) {
      return column.receivedValuesTimeStamp >= column.requestedValuesTimeStamp;
    }

    function isAllDataReceived() {
      return isAllColumnDataReceieved(timeColumn) && (! dataColumn || isAllColumnDataReceieved(dataColumn));
    }

    function connectedSensors() {
      var sensors = [],
          dataset = labquest2Interface.datasets[0],
          i, unit;

      for (i=0; i < dataset.columns.length; i++) {
        sensors.push(dataset.columns[i].units);
      }
      return sensors;
    }

    model = {

      on: function(type, listener) {
        dispatch.on(type, listener);
      },

      connect: function(address) {
        handle('connect', address);
      },

      start: function() {
        handle('start');
      },

      stop: function() {
        handle('stop');
      },

      tare: function() {
        var oldPlayable = isPlayable;
        isPlayable = false;
        handle('tare');
        isPlayable = oldPlayable;
      },

      willReset: function() {
        dispatch.willReset();
      },

      reset: function() {
        handle('reset');
      },

      reload: function() {
        model.stop();
        model.makeInvalidatingChange(function() {
          needsReload = true;
        });
      },

      isStopped: function() {
        return isStopped;
      },

      stepCounter: function() {
        return stepCounter;
      },

      connectedSensors: connectedSensors,
      getSelectedSensor: function() { return selectedSensor.index; },
      setSelectedSensor: function(sensorIndex) {
        if (selectedSensor.index !== sensorIndex) {
          selectedSensor.index = sensorIndex;
          selectedSensor.units = null;
          model.properties.tareValue = 0; // Also reset our tare value
          setColumn();
        }
      },

      serialize: function () { return ""; }
    };


    stateMachine = new StateMachine({

      notConnected: {
        enterState: function() {
          message = i18n.t("sensor.messages.not_connected");
          canConnect = true;
        },

        leaveState: function() {
          canConnect = false;
        },

        connect: function(address) {
          labquest2Interface.startPolling(address);
          this.gotoState('connecting');
        }
      },

      connecting: {
        enterState: function() {
          message = i18n.t("sensor.messages.connecting");
          if (labquest2Interface.isConnected) {
            this.gotoState('connected');
          }
        },

        statusErrored: function() {
          this.gotoState('initialConnectionFailure');
        },

        connectionTimedOut: function() {
          this.gotoState('initialConnectionFailure');
        },

        statusReceived: function() {
          this.gotoState('connected');
        },

        sessionChanged: function() {
          // start a new session, stay connecting...
          labquest2Interface.stopPolling();
          labquest2Interface.startPolling();
        }
      },

      initialConnectionFailure: {
        enterState: function() {
          labquest2Interface.stopPolling();
          message = i18n.t("sensor.messages.connection_failed");
          notifier.alert(i18n.t("sensor.messages.connection_failed_labquest2_alert"), {
            OK: function() {
              $(this).dialog("close");
              handle('dismiss');
            }
          });
        },

        dismiss: function() {
          this.gotoState('notConnected');
        }
      },

      connected: {
        enterState: function() {
          message = i18n.t("sensor.messages.connected");
          canTare = true;
          isPlayable = true;
          isStopped = true;

          setColumn();

          if (canControl) {
            this.controlEnabled();
          } else {
            this.controlDisabled();
          }

          if (labquest2Interface.isCollecting) {
            this.gotoState('started');
          }
        },

        leaveState: function() {
          canTare = false;
          isPlayable = false;
        },

        // Give some feedback on the currently selected column from which data will be collected.
        columnAdded: setColumn,
        columnRemoved: setColumn,
        columnTypeChanged: setColumn,
        columnMoved: setColumn,

        tare: function() {
          if (dataColumn) {
            if (liveMode) {
              model.properties.tareValue = dataColumn.liveValue;
            } else {
              canTare = false;
              // Display a message that we need to be in liveMode before we can tare
              notifier.alert(i18n.t("sensor.messages.tare_labquest2_alert"), {
                OK: function() {
                  $(this).dialog("close");
                  canTare = true;
                }
              });
            }
          }
        },

        // User requests collection
        start: function() {
          // NOTE. Due to architecture switch mid-way, the labquest2Interface layer is turning the
          // start request into a promise, and we're turning it back to events. The lower layer
          // could just ditch promises and emit the corresponding events with no harm. (The state
          // machine prevents almost every practical scenario where we'd see an out-of-date
          // startRequestFailure event while in a state that would respond to it.)
          labquest2Interface.requestStart().catch(function() {
            handle('startRequestFailed');
          });
          this.gotoState('starting');
        },

        controlEnabled: function() {
          message = i18n.t("sensor.messages.connected");
        },

        controlDisabled: function() {
          message = i18n.t("sensor.messages.connected_start_labquest2");
        },

        sessionChanged: function() {
          labquest2Interface.stopPolling();
          labquest2Interface.startPolling();
          this.gotoState('connecting');
        },

        // Collection was started by a third party
        collectionStarted: function() {
          this.gotoState('started');
        }
      },

      starting: {
        enterState: function() {
          message = i18n.t("sensor.messages.starting_data_collection");
          isStopped = false;
          var self = this;
          this._startTimerId = setTimeout(3000, function() {
            self.gotoState('startRequestFailed');
          });
        },

        leaveState: function() {
          clearTimeout(this._startTimerId);
        },

        startRequestFailed: function() {
          this.gotoState('errorStarting');
        },

        stop: function() {
          this.gotoState('canceling');
        },

        collectionStarted: function() {
          this.gotoState('started');
        }
      },

      errorStarting: {
        enterState: function() {
          message = i18n.t("sensor.messages.error_starting_data_collection");
          isStopped = true;

          notifier.alert(i18n.t("sensor.messages.error_starting_data_collection_alert"), {
            OK: function() {
              $(this).dialog("close");
              handle('dismissErrorStarting');
            }
          });
        },

        collectionStarted: function() {
          this.gotoState('started');
        },

        dismissErrorStarting: function() {
          this.gotoState('connected');
        }
      },

      started: {
        enterState: function() {
          if (canControl) {
            message = i18n.t("sensor.messages.collecting_data");
          } else {
            message = i18n.t("sensor.messages.collecting_data_stop_labquest2");
          }
          isStopped = false;
          setColumn();

          // Check, just in case. Specifically, when errorStopping transitions here, collection
          // might have stopped in the meantime.
          if ( ! labquest2Interface.isCollecting ) {
            this.gotoState('stopped');
          }

          if ( ! dataColumn ) {
            this.gotoState('startedWithNoDataColumn');
          }
        },

        data: handleData,

        controlEnabled: function() {
          message = i18n.t("sensor.messages.collecting_data");
        },

        controlDisabled: function() {
          message = i18n.t("sensor.messages.collecting_data_stop_labquest2");
        },

        stop: function() {
          labquest2Interface.requestStop().catch(function() {
            handle('stopRequestFailed');
          });
          this.gotoState('stopping');
        },

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        }
      },

      // This can happen.
      startedWithNoDataColumn: {
        enterState: function() {
          message = i18n.t("sensor.messages.no_data");

          labquest2Interface.requestStop();
          notifier.alert(i18n.t("sensor.messages.no_data_labquest2_alert"), {
            OK: function() {
              $(this).dialog("close");
            }
          });
        },

        collectionStopped: function() {
          this.gotoState('stoppedWithNoDataColumn');
        }
      },

      stoppedWithNoDataColumn: {
        enterState: function() {
          if (isAllDataReceived()) {
            this.gotoState('connected');
          }
        },

        data: function() {
          if (isAllDataReceived()) {
            this.gotoState('connected');
          }
        }
      },

      canceling: {
        enterState: function() {
          message = i18n.t("sensor.messages.canceling_data_collection");
          isStopped = true;
        },

        data: handleData,

        cancelRequestFailed: function() {
          this.gotoState('errorCanceling');
        },

        collectionStarted: function() {
          labquest2Interface.requestStop().catch(function() {
            handle('cancelRequestFailed');
          });
        },

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        }
      },

      errorCanceling: {
        enterState: function() {
          message = i18n.t("sensor.messages.error_canceling_data_collection");
          notifier.alert(i18n.t("sensor.messages.error_canceling_data_collection_alert"), {
            OK: function() {
              $(this).dialog("close");
              handle('dismissErrorStopping');
            }
          });
        },

        data: handleData,

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        },

        dismissErrorStopping: function() {
          this.gotoState('started');
        }
      },

      stopping: {
        enterState: function() {
          message = i18n.t("sensor.messages.stopping_data_collection");
        },

        data: handleData,

        stopRequestFailed: function() {
          this.gotoState('errorStopping');
        },

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        }
      },

      errorStopping: {
        enterState: function() {
          message = i18n.t("sensor.messages.error_stopping_data_collection");
          notifier.alert(i18n.t("sensor.messages.error_stopping_data_collection_alert"), {
            OK: function() {
              $(this).dialog("close");
              handle('dismissErrorStopping');
            }
          });
        },

        data: handleData,

        collectionStopped: function() {
          this.gotoState('collectionStopped');
        },

        dismissErrorStopping: function() {
          this.gotoState('started');
        }
      },

      // The device reports the stop of data collection before all data can be received.
      collectionStopped: {
        enterState: function() {
          message = i18n.t("sensor.messages.data_collection_stopped");
          if (isAllDataReceived()) {
            this.gotoState('collectionComplete');
          }
        },

        data: function() {
          handleData();
          if (isAllDataReceived()) {
            this.gotoState('collectionComplete');
          }
        }
      },

      collectionComplete: {
        enterState: function() {
          message = i18n.t("sensor.messages.data_collection_complete");
          isStopped = true;
        },

        reset: function() {
          initializeStateVariables();
          setSensorReadingDescription();
          this.gotoState('connecting');
          dispatch.reset();
        }
      },

      disconnected: {
        enterState: function() {
          message = i18n.t("sensor.messages.disconnected");
          canConnect = true;
          labquest2Interface.stopPolling();
        },

        leaveState: function() {
          canConnect = false;
        },

        connect: function(address) {
          labquest2Interface.startPolling(address);
          this.gotoState('connecting');
        }
      }
    });

    // Automatically wrap all event handlers invocations with makeInvalidatingChange so that
    // outputs update from closure variable state automatically.
    function handle(eventName) {
      var args = Array.prototype.slice.call(arguments, 0);

      model.makeInvalidatingChange(function() {
        var handled = stateMachine.handleEvent.apply(stateMachine, args);

        if ( ! handled ) {
          // special handling of any events not handled by the current state:
          if (eventName === 'connectionTimedOut' ||
              eventName === 'sessionChanged') {
            stateMachine.gotoState('disconnected');
          }
        }
      });
    }

    // At least for now, dispatch every interface event to the state machine.
    labquest2Interface.on('*', function() {
      var args = Array.prototype.slice.call(arguments, 0);
      handle.apply(null, [this.event].concat(args));
    });

    // Also, handle "live values" every time they are received.
    labquest2Interface.on('statusReceived', function() {
      if (dataColumn) {
        model.makeInvalidatingChange(function() {
          // Figure out if we're in "live" mode, or if we're still viewing old data
          // (and therefore the live values aren't actually updating)
          var now = new Date();
          if ((dataColumn.liveValueTimeStamp - lastLiveValueTimestamp) > 0) {
            liveMode = true;
            lastLiveValueTimestamp = dataColumn.liveValueTimeStamp;
            liveValueLastChanged = now;
          } else {
            if ((now - liveValueLastChanged) < 2000) {
              liveMode = true;
            } else {
              liveMode = false;
            }
          }
          liveSensorValue = dataColumn.liveValue;
        });
      }
    });

    labquest2Interface.on('controlEnabled',  function() {
      canControl = true;
    });

    labquest2Interface.on('controlDisabled', function() {
      canControl = false;
    });

    labModelerMixin = new LabModelerMixin({
      metadata: metadata,
      setters: {},
      unitsDefinition: unitsDefinition,
      initialProperties: initialProperties,
      usePlaybackSupport: false
    });

    labModelerMixin.mixInto(model);
    propertySupport = labModelerMixin.propertySupport;
    dispatch = labModelerMixin.dispatchSupport;
    dispatch.addEventTypes("tick", "play", "stop", "tickStart", "tickEnd");

    initializeStateVariables();

    model.defineOutput('time', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.2f'
    }, function() {
      return time;
    });

    model.defineOutput('displayTime', {
      label: i18n.t("sensor.measurements.time"),
      unitType: 'time',
      format: '.2f'
    }, function() {
      return time;
    });

    model.defineOutput('sensorReading', defaultSensorReadingDescription, function() {
      if (rawSensorValue == null) {
        return rawSensorValue;
      }
      return rawSensorValue - model.properties.tareValue;
    });

    // Because sensorReading updates are batched and delivered much later than the live sensor value
    // from the sensor status response, we define a separate liveSensorReading output that can be
    // updated every time the status is polled.
    model.defineOutput('liveSensorReading', defaultSensorReadingDescription, function() {
      if (liveSensorValue == null) {
        return liveSensorValue;
      }
      return liveSensorValue - model.properties.tareValue;
    });

    model.defineOutput('sensorName', {
      label: "Sensor Name"
    }, function() {
      return sensorName;
    });

    model.defineOutput('isStopped', {
      label: "Stopped?"
    }, function() {
      return isStopped;
    });

    // TODO. We need a way to make "model-writable" read only properties.
    model.defineOutput('isPlayable', {
      label: "Startable?"
    }, function() {
      return isPlayable;
    });

    model.defineOutput('hasPlayed', {
      label: "Has successfully collected data?"
    }, function() {
      return stepCounter > 0;
    });

    model.defineOutput('canTare', {
      label: "Can set a tare value?"
    }, function() {
      return canTare && isSensorTareable;
    });

    model.defineOutput('canConnect', {
      label: "Can begin connecting to the LabQuest2?"
    }, function() {
      return canConnect;
    });

    model.defineOutput('canControl', {
      label: "Can remotely start/stop the LabQuest2?"
    }, function() {
      return canControl;
    });

    model.defineOutput('hasMultipleSensors', {
      label: "Are multiple sensors connected to the LabQuest2?"
    }, function() {
      return hasMultipleSensors;
    });

    model.defineOutput('needsReload', {
      label: "Needs Reload?"
    }, function() {
      return needsReload;
    });

    model.defineOutput('message', {
      label: "User Message"
    }, function() {
      return message;
    });

    // Clean up state before we go
    // TODO
    model.on('willReset.model', function() {
      labquest2Interface.stopPolling();
      labquest2Interface.requestStop();
    });

    model.updateAllOutputProperties();
    stateMachine.gotoState('notConnected');

    return model;
  };
});

define('models/labquest2/view',['require','common/views/numeric-output-view','common/controllers/basic-dialog','models/sensor-common/i18n-sensor-definitions-connector','common/views/view-state'],function(require) {

  var NumericOutputView = require('common/views/numeric-output-view'),
      BasicDialog       = require('common/controllers/basic-dialog'),
      getSensorDefinitions = require('models/sensor-common/i18n-sensor-definitions-connector'),
      viewState = require('common/views/view-state');

  return function(model, modelUrl, i18n) {

    // TODO use the formatter from the property description. Right now, it automatically adds
    // units to the returned string (which we don't want here).
    var format = d3.format('.2f');
    var sensorReadingView;
    var view;

    var sensorDefinitions = getSensorDefinitions(i18n);

    function setCanTareState() {
      if (model.properties.canTare) {
        viewState.enableView(view.$zeroButton);
      } else {
        viewState.disableView(view.$zeroButton);
      }
    }

    function setCanConnectState() {
      if (model.properties.canConnect) {
        viewState.enableView(view.$connectButton);
      } else {
        viewState.disableView(view.$connectButton);
      }
    }

    function setHasMultipleSensorsState() {
      if (model.properties.hasMultipleSensors && model.properties.isPlayable) {
        viewState.enableView(view.$selectSensorButton);
      } else {
        viewState.disableView(view.$selectSensorButton);
      }
    }

    function setMessageText() {
      view.$message.text(model.properties.message);
    }

    function setupModelObservers() {
      model.addObserver('canTare', setCanTareState);
      setCanTareState();

      model.addObserver('canConnect', setCanConnectState);
      setCanConnectState();

      model.addObserver('hasMultipleSensors', setHasMultipleSensorsState);
      model.addObserver('isPlayable', setHasMultipleSensorsState);
      setHasMultipleSensorsState();

      model.addObserver('message', setMessageText);
      setMessageText();
    }

    function chooseSensorPopup() {
      var dialog = new BasicDialog({
        width: "60%",
        dialogClass: 'interactive-dialog no-close',
        closeOnEscape: false,
        title: i18n.t("sensor.choose_sensor_title"),
        buttons: {
          OK: function() {
            console.log("OK Clicked");
            $(this).dialog("close");
            // TODO Change the model's selected sensor
            model.setSelectedSensor($(this).find('input:checked').val());
          },
          Cancel: function() {
            console.log("Cancel Clicked");
            $(this).dialog("close");
            // TODO Make sure the model continues using the previously selected sensor
          }
        }
      }, i18n);
      var content = "",
          label = "",
          sensors = model.connectedSensors(),
          first = true,
          selectedSensor = model.getSelectedSensor(),
          i, checked, sensorDef;
      for (i = 0; i < sensors.length; i++) {
        if (sensors[i] !== 's') {
          checked = "";
          sensorDef = sensorDefinitions[sensors[i]];
          if (!sensorDef) {
            sensorDef = { measurementName: "Unknown" };
          }
          label = sensorDef.measurementName + " (" + sensors[i] + ")";
          if (selectedSensor == i || (first && selectedSensor == -1)) {
            checked = "checked ";
          };
          content += "<input type='radio' name='selected-sensor-index' value='" + i + "' " + checked + "/>" + label + "<br/>";
          first = false;
        }
      }
      dialog.setContent(content);
      dialog.open();
    }

    return view = {
      $el: $("<div id='model-container' class='container sensor-model-container' />"),
      remoteAddress: null,

      bindModel: function(newModel, newModelUrl) {
        modelUrl = newModelUrl || modelUrl;
        model = newModel || model;

        setupModelObservers();
      },

      getHeightForWidth: function() {
        return "2.8em";
      },

      // called once we're in the DOM
      setup: function() {
        view.$el.empty();
        view.$controlsContainer = $("<div></div>");
        view.$statusContainer = $("<div></div>");
        view.$addressInput = $("<div class='address-input'><input type='text' name='address-input' placeholder='" + i18n.t("sensor.address_labquest2") + "'></input></div>");
        sensorReadingView = new NumericOutputView({
          id: 'sensor-value-view',
          label: i18n.t("sensor.reading"),
          units: model.getPropertyDescription('sensorReading').getUnitAbbreviation()
        });

        view.$connectButton = $("<div class='interactive-button'><button>" + i18n.t("sensor.connect") + "</button></div>");
        view.$zeroButton = $("<div class='interactive-button'><button>" + i18n.t("sensor.zero") + "</button></div>");
        view.$selectSensorButton = $("<div class='interactive-button'><button>" + i18n.t("sensor.select_sensor") + "</button></div>");
        view.$message = $("<div class='message'></div>");
        view.$sensorReading = sensorReadingView.render().addClass("horizontal");

        view.$controlsContainer
          .append(view.$addressInput)
          .append(view.$connectButton)
          .append(view.$sensorReading)
          .append(view.$zeroButton)
          .append(view.$selectSensorButton);

        view.$statusContainer
          .append(view.$message);

        view.$el.css('zIndex', 4)
          .append(view.$controlsContainer)
          .append(view.$statusContainer);

        view.$controlsContainer.find('div').addClass('component component-spacing');
        view.$statusContainer.find('div').addClass('component component-spacing');

        sensorReadingView.resize();
        setupModelObservers();

        if (typeof(view.remoteAddress) == "string") {
          view.$addressInput.find('input').val(view.remoteAddress);
        }
        view.$addressInput.find('input').on('change', function() {
          view.remoteAddress = $(this).val();
        });

        // connect when the user hits enter
        view.$addressInput.find('input').on('keypress', function(evt) {
          var code = evt.keyCode || evt.which || 0;
          if (code == 13 && model.properties.canConnect) {
            model.connect(view.$addressInput.find('input').val());
            evt.preventDefault();
          }
        });

        view.$connectButton.on('click', 'button', function() {
          model.connect(view.$addressInput.find('input').val());
        });
        view.$zeroButton.on('click', 'button', model.tare);
        view.$selectSensorButton.on('click', 'button', chooseSensorPopup);
      },

      resize: function() {
        if (sensorReadingView) {
          sensorReadingView.resize();
        }
      },

      repaint: function() {},

      setFocus: function () {},

      updateUnits: function(units) {
        sensorReadingView.updateUnits(units);
        if (model.properties.liveSensorReading == null) {
          sensorReadingView.hideUnits();
        }
      },

      update: function() {
        if (model.properties.liveSensorReading == null) {
          sensorReadingView.update("");
          sensorReadingView.hideUnits();
        } else {
          sensorReadingView.update(format(model.properties.liveSensorReading));
          sensorReadingView.showUnits();
        }
      }
    };
  };
});

define('models/labquest2/scripting-api',[],function () {
  /**
    Define the model-specific Sensor scripting API used by 'action' scripts on interactive elements.

    The universal Interactive scripting API is extended with the properties of the
    object below which will be exposed to the interactive's 'action' scripts as if
    they were local vars. All other names (including all globals, but excluding
    Javascript builtins) will be unavailable in the script context; and scripts
    are run in strict mode so they don't accidentally expose or read globals.

    @param: parent Common Scripting API
  */
  return function SensorScriptingAPI (parent) {
    return {
      /**
       * Reset the model. The interactives controller will emit a 'willResetModel'.
       * The willResetModel observers can ask to wait for asynchronous confirmation before
       * the model is actually reset.
       * Note that the effect would be almost the same like after reload operation. However
       * the mechanism under the hood is completely different. The biggest difference for
       * authoring is that when .resetModel() is called, the "onLoad" script won't be
       * executed again. It may be also faster than reload.
       * @param  {object} options hash of options, supported properties:
       *                         * propertiesToRetain - a list of properties to save before
       *                           the model reset and restore after reset.
       *                         * cause - cause of the reset action.
       */
      resetModel: function resetModel(options) {
        parent.intController.resetModel(options);
      }
    };
  };
});

/*global define */

define('models/labquest2/controller',['require','common/controllers/model-controller','./modeler','./view','./scripting-api'],function (require) {
  // Dependencies.
  var ModelController   = require('common/controllers/model-controller'),
      Model             = require('./modeler'),
      ModelContainer    = require('./view'),
      ScriptingAPI      = require('./scripting-api');

  return function (modelUrl, modelOptions, interactiveController) {
    var controller = new ModelController(modelUrl, modelOptions, interactiveController,
      Model, ModelContainer, ScriptingAPI);

    // Note to self: modelController doesn't emit modelLoaded when the model first loads.
    // This was unexpected...

    function setupModelObservers() {
      var model = controller.model;

      model.addObserver('liveSensorReading', function() {
        controller.updateView();
      });

      model.addObserver('needsReload', function() {
        if (model.properties.needsReload) {
          interactiveController.reloadModel();
        }
      });

      model.addPropertyDescriptionObserver('sensorReading', function() {
        var description = model.getPropertyDescription('sensorReading');
        var view = controller.modelContainer;

        view.updateUnits(description.getUnitAbbreviation());
      });
    }

    interactiveController.on('modelLoaded.labquest2-model-controller', setupModelObservers);

    interactiveController.on('modelReset.labquest2-model-controller', function() {
      controller.model.set('isNewRunInProgress', false);
    });

    interactiveController.on('willResetModel', function() {
      controller.model.set('isNewRunInProgress', true);
      controller.model.willReset();
    });

    return controller;
  };
});

/*jslint indent: 2, browser: true, newcap: true */
/*globals define: false*/

// Basic constants used by Energy2D module

define('models/energy2d/models/constants',['require','exports','module'],function (require, exports, module) {
  'use strict';
  // Air's thermal conductivity = 0.025 W/(m*K)
  exports.AIR_THERMAL_CONDUCTIVITY = 0.025;
  // Air's specific heat = 1012 J/(kg*K)
  exports.AIR_SPECIFIC_HEAT = 1012;
  // Air's density = 1.204 kg/m^3 at 25 C
  exports.AIR_DENSITY = 1.204;
  // By default, air's kinematic viscosity = 1.568 x 10^-5 m^2/s at 27 C is
  // used. It can be set to zero for inviscid fluid.
  exports.AIR_VISCOSITY = 0.00001568;
  // Stefan's constant unit J/(s*m^2*K^-4)
  exports.STEFAN_CONSTANT = 0.0000000567;
});

/*global define: false */

define('models/energy2d/metadata',['require','models/energy2d/models/constants'],function(require) {
  var constants = require('models/energy2d/models/constants');

  return {
    mainProperties: {
      type: {
        defaultValue: "energy2d",
        immutable: true
      },
      use_WebGL: {
        defaultValue: false
      },
      grid_width: {
        defaultValue: 100,
        immutable: true
      },
      grid_height: {
        defaultValue: 100,
        immutable: true
      },
      model_width: {
        defaultValue: 10,
        immutable: true
      },
      model_height: {
        defaultValue: 10,
        immutable: true
      },
      timeStep: {
        defaultValue: 1
      },
      timeStepsPerTick: {
        defaultValue: 4
      },
      ticksPerGPUSync: {
        defaultValue: 30
      },
      convective: {
        defaultValue: true
      },
      background_temperature: {
        defaultValue: 0
      },
      background_conductivity: {
        defaultValue: constants.AIR_THERMAL_CONDUCTIVITY
      },
      background_specific_heat: {
        defaultValue: constants.AIR_SPECIFIC_HEAT
      },
      background_density: {
        defaultValue: constants.AIR_DENSITY
      },
      background_viscosity: {
        defaultValue: constants.AIR_VISCOSITY
      },
      thermal_buoyancy: {
        defaultValue: 0.00025
      },
      buoyancy_approximation: {
        defaultValue: 1
      },
      boundary: {
        defaultValue: {
          type: "temperature",
          upper: 0,
          lower: 0,
          left: 0,
          right: 0
        }
      },
      sunny: {
        defaultValue: false
      },
      sun_angle: {
        defaultValue: 1.5707964
      },
      solar_power_density: {
        defaultValue: 2000
      },
      solar_ray_count: {
        defaultValue: 24
      },
      solar_ray_speed: {
        defaultValue: 0.1
      },
      photon_emission_interval: {
        defaultValue: 20
      }
    },

    viewOptions: {
      showClock: {
        defaultValue: true,
        propertyChangeInvalidates: false
      },
      controlButtons: {
        defaultValue: "play_reset",
        propertyChangeInvalidates: false
      },
      color_palette_type: {
        defaultValue: 0
      },
      velocity: {
        defaultValue: false
      },
      minimum_temperature: {
        defaultValue: 0
      },
      maximum_temperature: {
        defaultValue: 40.0
      },
      enableKeyboardHandlers: {
        defaultValue: true
      }
    },

    part: {
      shapeType: {
        // Available options: "rectangle", "ellipse", "ring" or "polygon".
        required: true,
        immutable: true
      },
      x: {
        defaultValue: 0
      },
      y: {
        defaultValue: 0
      },
      width: {},
      height: {},
      a: {},
      b: {},
      inner: {},
      outer: {},
      vertices: {},

      // Special shortcut properties which let user access polygon properties
      // faster and more convenient. They can be used both to get and set
      // value. They are not serialized.
      raw_x_coords: {
        serialize: false
      },
      raw_y_coords: {
        serialize: false
      },

      thermal_conductivity: {
        defaultValue: 1
      },
      specific_heat: {
        defaultValue: 1300
      },
      density: {
        defaultValue: 25
      },
      transmission: {
        defaultValue: 0
      },
      reflection: {
        defaultValue: 0
      },
      absorption: {
        defaultValue: 1
      },
      emissivity: {
        defaultValue: 0
      },
      temperature: {
        defaultValue: 0
      },
      constant_temperature: {
        defaultValue: false
      },
      power: {
        defaultValue: 0
      },
      wind_speed: {
        defaultValue: 0
      },
      wind_angle: {
        defaultValue: 0
      },
      visible: {
        defaultValue: true
      },
      filled: {
        defaultValue: true
      },
      color: {
        // Auto color means that part will use color matching its power or temperature
        // (when it has constant temperature) or gray color otherwise.
        defaultValue: "auto"
      },
      label: {
        defaultValue: ""
      },
      texture: {
        defaultValue: false
      },
      draggable: {
        defaultValue: true
      }
    },

    sensor: {
      type: {
        required: true,
        immutable: true
      },
      x: {
        required: true,
        unitType: "length"
      },
      y: {
        required: true,
        unitType: "length"
      },
      angle: {
        // Optional, defined only for heat flux sensors.
        immutable: true
      },
      label: {
        defaultValue: ""
      },
      value: {
        readOnly: true,
        serialize: false
      }
    }
  };
});

/*global define: false*/

define('models/energy2d/models/physics-solvers/heat-solver',['require','exports','module','arrays'],function (require, exports) {
  'use strict';
  var
    arrays = require('arrays'),

    RELAXATION_STEPS = 5;

  exports.makeHeatSolver = function (model) {
    var
      // Basic simulation parameters.
      props = model.getModelOptions(),
      nx = props.grid_width,
      ny = props.grid_height,

      relaxationSteps = RELAXATION_STEPS,

      // Simulation arrays provided by model.
      conductivity = model.getConductivityArray(),
      capacity     = model.getCapacityArray(),
      density      = model.getDensityArray(),
      u            = model.getUVelocityArray(),
      v            = model.getVVelocityArray(),
      tb           = model.getBoundaryTemperatureArray(),
      fluidity     = model.getFluidityArray(),

      // Internal array that stores the previous temperature results.
      t0 = arrays.create(nx * ny, 0, model.getArrayType()),

      // Convenience variables.
      nx1 = nx - 1,
      ny1 = ny - 1,
      nx2 = nx - 2,
      ny2 = ny - 2,

      deltaX = props.model_width / props.grid_width,
      deltaY = props.model_height / props.grid_height,

      //
      // Private methods
      //

      applyBoundary  = function (t) {
        var
          boundary = props.boundary,
          vN = boundary.upper,
          vS = boundary.lower,
          vW = boundary.left,
          vE = boundary.right,
          i, j, inx, inx_ny1;

        if (boundary.type === "temperature") {
          for (i = 0; i < nx; i += 1) {
            inx = i * nx;
            t[inx] = vN;
            t[inx + ny1] = vS;
          }
          for (j = 0; j <  ny; j += 1) {
            t[j] = vW;
            t[nx1 * nx + j] = vE;
          }
        } else if (boundary.type === "flux") {
          for (i = 0; i < nx; i += 1) {
            inx = i * nx;
            inx_ny1 = inx + ny1;
            t[inx] = t[inx + 1] + vN * deltaY / conductivity[inx];
            t[inx_ny1] = t[inx + ny2] - vS * deltaY / conductivity[inx_ny1];
          }
          for (j = 0; j < ny; j += 1) {
            t[j] = t[nx + j] - vW * deltaX / conductivity[j];
            t[nx1 * nx + j] = t[nx2 * nx + j] + vE * deltaX / conductivity[nx1 * nx + j];
          }
        }
      },

      macCormack  = function (t) {
        var
          timeStep = props.timeStep,
          tx = 0.5 * timeStep / deltaX,
          ty = 0.5 * timeStep / deltaY,
          i, j, inx, jinx, jinx_plus_nx, jinx_minus_nx, jinx_plus_1, jinx_minus_1;

        for (i = 1; i < nx1; i += 1) {
          inx = i * nx;
          for (j = 1; j < ny1; j += 1) {
            jinx = inx + j;
            jinx_minus_nx = jinx - nx;
            jinx_plus_nx = jinx + nx;
            jinx_minus_1 = jinx - 1;
            jinx_plus_1 = jinx + 1;
            if (fluidity[jinx]) {
              t0[jinx] = t[jinx]
                - tx * (u[jinx_plus_nx] * t[jinx_plus_nx] - u[jinx_minus_nx] * t[jinx_minus_nx])
                - ty * (v[jinx_plus_1] * t[jinx_plus_1] - v[jinx_minus_1] * t[jinx_minus_1]);
            }
          }
        }
        applyBoundary(t0);

        for (i = 1; i < nx1; i += 1) {
          inx = i * nx;
          for (j = 1; j < ny1; j += 1) {
            jinx = inx + j;
            if (fluidity[jinx]) {
              jinx_minus_nx = jinx - nx;
              jinx_plus_nx = jinx + nx;
              jinx_minus_1 = jinx - 1;
              jinx_plus_1 = jinx + 1;

              t[jinx] = 0.5 * (t[jinx] + t0[jinx]) - 0.5 * tx * u[jinx]
                * (t0[jinx_plus_nx] - t0[jinx_minus_nx]) - 0.5 * ty * v[jinx]
                * (t0[jinx_plus_1] - t0[jinx_minus_1]);
            }
          }
        }
        applyBoundary(t);
      };

    return {
      solve: function (convective, t, q) {
        var
          timeStep = props.timeStep,
          hx = 0.5 / (deltaX * deltaX),
          hy = 0.5 / (deltaY * deltaY),
          invTimeStep = 1.0 / timeStep,
          rij, sij, axij, bxij, ayij, byij,
          k, i, j, inx, jinx, jinx_plus_nx, jinx_minus_nx, jinx_plus_1, jinx_minus_1;

        arrays.copy(t, t0);

        for (k = 0; k < relaxationSteps; k += 1) {
          for (i = 1; i < nx1; i += 1) {
            inx = i * nx;
            for (j = 1; j < ny1; j += 1) {
              jinx = inx + j;
              if (isNaN(tb[jinx])) {
                jinx_minus_nx = jinx - nx;
                jinx_plus_nx = jinx + nx;
                jinx_minus_1 = jinx - 1;
                jinx_plus_1 = jinx + 1;

                sij = capacity[jinx] * density[jinx] * invTimeStep;
                rij = conductivity[jinx];
                axij = hx * (rij + conductivity[jinx_minus_nx]);
                bxij = hx * (rij + conductivity[jinx_plus_nx]);
                ayij = hy * (rij + conductivity[jinx_minus_1]);
                byij = hy * (rij + conductivity[jinx_plus_1]);
                t[jinx] = (t0[jinx] * sij + q[jinx] + axij * t[jinx_minus_nx] + bxij
                          * t[jinx_plus_nx] + ayij * t[jinx_minus_1] + byij * t[jinx_plus_1])
                          / (sij + axij + bxij + ayij + byij);
              } else {
                t[jinx] = tb[jinx];
              }
            }
          }
          applyBoundary(t);
        }
        if (convective) {
          // advect(t)
          macCormack(t);
        }
      }
    };
  };
});

/*global define: false*/

// WebGL Context Manager module.
//
// It provides access to one, global WebGL context.
// All clients interested in WebGL context should call:
// getWebGLContext() function. If WebGL is not available,
// an appropriate error will be thrown.
define('models/energy2d/gpu/context',[],function () {
  'use strict';
  // The internal `gl` variable holds the current WebGL context.
  var gl;

  return {
    getWebGLContext: function () {
      if (!gl) {
        var canvas = document.createElement('canvas');
        try {
          gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
        } catch (e) {}
        if (!gl) {
          throw new Error('GL: WebGL not supported.');
        }
      }
      return gl;
    },

    get error() {
      if (!gl) return "WebGL unavailable";
      var error = gl.getError();
      return error === gl.NO_ERROR ? undefined : error;
    }
  };
});

/*jslint indent: 2, browser: true, newcap: true, es5: true */
/*globals define: false, Float32Array: false, Uint16Array: false, console: false*/

define('models/energy2d/gpu/shader',['require','common/console','models/energy2d/gpu/context'],function (require) {
  'use strict';
  var
    // Dependencies.
    console = require('common/console'),
    context = require('models/energy2d/gpu/context'),

    // The internal `gl` variable holds the current WebGL context.
    gl,

    // Local, private helper functions.
    regexMap = function (regex, text, callback) {
      var result;
      while ((result = regex.exec(text)) !== null) {
        callback(result);
      }
    },
    isArray = function (obj) {
      var str = Object.prototype.toString.call(obj);
      return str === '[object Array]' || str === '[object Float32Array]';
    },
    isNumber = function (obj) {
      var str = Object.prototype.toString.call(obj);
      return str === '[object Number]' || str === '[object Boolean]';
    },
    // Class to be exported.
    Shader;

  // Compiles a shader program using the provided vertex and fragment shaders.
  Shader = function (vertexSource, fragmentSource) {
    var
      // Headers are prepended to the sources to provide some automatic functionality.
      vertexHeader =
      '\
      attribute vec4 gl_Vertex;\
      attribute vec4 gl_TexCoord;\
      attribute vec3 gl_Normal;\
      attribute vec4 gl_Color;\
      ',
      fragmentHeader =
      '\
      precision highp float;\
      ',

      // The `gl_` prefix must be substituted for something else to avoid compile
      // errors, since it's a reserved prefix. This prefixes all reserved names with
      // `_`. The header is inserted after any extensions, since those must come
      // first.
      fix = function (header, source) {
        var replaced = {}, match;
        match = /^((\s*\/\/.*\n|\s*#extension.*\n)+)[^]*$/.exec(source);
        source = match ? match[1] + header + source.substr(match[1].length) : header + source;
        regexMap(/\bgl_\w+\b/g, header, function (result) {
          if (replaced[result] === undefined) {
            source = source.replace(new RegExp('\\b' + result + '\\b', 'g'), '_' + result);
            replaced[result] = true;
          }
        });
        return source;
      },

      isSampler = {};

    gl = context.getWebGLContext();

    vertexSource = fix(vertexHeader, vertexSource);
    fragmentSource = fix(fragmentHeader, fragmentSource);

    // Compile and link errors are thrown as strings.
    function compileSource(type, source) {
      var shader = gl.createShader(type);
      gl.shaderSource(shader, source);
      gl.compileShader(shader);
      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        throw new Error('Shader: compile error.\n' + gl.getShaderInfoLog(shader) +
                        '\nSource:\n' + source);
      }
      return shader;
    }

    this.program = gl.createProgram();
    gl.attachShader(this.program, compileSource(gl.VERTEX_SHADER, vertexSource));
    gl.attachShader(this.program, compileSource(gl.FRAGMENT_SHADER, fragmentSource));
    gl.linkProgram(this.program);
    if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
      throw new Error('Shader: link error.\n' + gl.getProgramInfoLog(this.program) +
                      '\nSource:\n' + vertexSource + '\n\n' + fragmentSource);
    }
    this.attributes = {};
    this.uniformLocations = {};

    // Sampler uniforms need to be uploaded using `gl.uniform1i()` instead of `gl.uniform1f()`.
    // To do this automatically, we detect and remember all uniform samplers in the source code.
    regexMap(/uniform\s+sampler(1D|2D|3D|Cube)\s+(\w+)\s*;/g, vertexSource + fragmentSource, function (groups) {
      isSampler[groups[2]] = 1;
    });
    this.isSampler = isSampler;
  };

  // Set a uniform for each property of `uniforms`. The correct `gl.uniform*()` method is
  // inferred from the value types and from the stored uniform sampler flags.
  Shader.prototype.uniforms = function (uniforms) {
    var name, location, value;

    gl.useProgram(this.program);

    for (name in uniforms) {
      if (uniforms.hasOwnProperty(name)) {
        if (this.uniformLocations[name] === undefined) {
          this.uniformLocations[name] = gl.getUniformLocation(this.program, name);
        }
        location = this.uniformLocations[name];
        if (location === null) {
          console.warn('Shader: name ' + name + ' does not correspond to an active uniform variable.');
          continue;
        }
        value = uniforms[name];
        if (isArray(value)) {
          switch (value.length) {
          case 1: gl.uniform1fv(location, new Float32Array(value)); break;
          case 2: gl.uniform2fv(location, new Float32Array(value)); break;
          case 3: gl.uniform3fv(location, new Float32Array(value)); break;
          case 4: gl.uniform4fv(location, new Float32Array(value)); break;
          // Matrices are automatically transposed, since WebGL uses column-major
          // indices instead of row-major indices.
          case 9: gl.uniformMatrix3fv(location, false, new Float32Array([
            value[0], value[3], value[6],
            value[1], value[4], value[7],
            value[2], value[5], value[8]
          ])); break;
          case 16: gl.uniformMatrix4fv(location, false, new Float32Array([
            value[0], value[4], value[8], value[12],
            value[1], value[5], value[9], value[13],
            value[2], value[6], value[10], value[14],
            value[3], value[7], value[11], value[15]
          ])); break;
          default: throw new Error('Shader: don\'t know how to load uniform "' + name + '" of length ' + value.length);
          }
        } else if (isNumber(value)) {
          (this.isSampler[name] ? gl.uniform1i : gl.uniform1f).call(gl, location, value);
        } else {
          throw new Error('Shader: attempted to set uniform "' + name + '" to invalid value ' + value);
        }
      }
    }

    return this;
  };

  // Sets all uniform matrix attributes, binds all relevant buffers, and draws the
  // mesh geometry as indexed triangles or indexed lines. Set `mode` to `gl.LINES`
  // (and either add indices to `lines` or call `computeWireframe()`) to draw the
  // mesh in wireframe.
  Shader.prototype.draw = function (mesh, mode) {
    gl.useProgram(this.program);

    this.drawBuffers(mesh.vertexBuffers,
      mesh.indexBuffers[mode === gl.LINES ? 'lines' : 'triangles'],
      arguments.length < 2 ? gl.TRIANGLES : mode);
  };

  // Sets all uniform matrix attributes, binds all relevant buffers, and draws the
  // indexed mesh geometry. The `vertexBuffers` argument is a map from attribute
  // names to `Buffer` objects of type `gl.ARRAY_BUFFER`, `indexBuffer` is a `Buffer`
  // object of type `gl.ELEMENT_ARRAY_BUFFER`, and `mode` is a WebGL primitive mode
  // like `gl.TRIANGLES` or `gl.LINES`. This method automatically creates and caches
  // vertex attribute pointers for attributes as needed.
  Shader.prototype.drawBuffers = function (vertexBuffers, indexBuffer, mode) {
    // Create and enable attribute pointers as necessary.
    var length = 0, attribute, buffer, location;

    for (attribute in vertexBuffers) {
      if (vertexBuffers.hasOwnProperty(attribute)) {
        buffer = vertexBuffers[attribute];
        if (this.attributes[attribute] === undefined) {
          this.attributes[attribute] = gl.getAttribLocation(this.program, attribute.replace(/^gl_/, '_gl_'));
        }
        location = this.attributes[attribute];
        if (location === -1 || !buffer.buffer) {
          continue;
        }
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
        gl.enableVertexAttribArray(location);
        gl.vertexAttribPointer(location, buffer.buffer.spacing, gl.FLOAT, false, 0, 0);
        length = buffer.buffer.length / buffer.buffer.spacing;
      }
    }

    // Disable unused attribute pointers.
    for (attribute in this.attributes) {
      if (this.attributes.hasOwnProperty(attribute)) {
        if (vertexBuffers[attribute] === undefined) {
          gl.disableVertexAttribArray(this.attributes[attribute]);
        }
      }
    }

    // Draw the geometry.
    if (length && (!indexBuffer || indexBuffer.buffer)) {
      if (indexBuffer) {
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer);
        gl.drawElements(mode, indexBuffer.buffer.length, gl.UNSIGNED_SHORT, 0);
      } else {
        gl.drawArrays(mode, 0, length);
      }
    }
  };

  // Export constructor function.
  return Shader;
});

/*jslint indent: 2, browser: true, newcap: true */
/*globals define: false*/

// Simple wrapper around WebGL textures that supports render-to-texture.
//
// The arguments `width` and `height` give the size of the texture in texels.
// WebGL texture dimensions must be powers of two unless `filter` is set to
// either `gl.NEAREST` or `gl.REPEAT` and `wrap` is set to `gl.CLAMP_TO_EDGE`
// (which they are by default).
//
// Texture parameters can be passed in via the `options` argument.
// Example usage:
//
//     var t = new Texture(256, 256, {
//       // Defaults to gl.LINEAR, set both at once with "filter"
//       mag_filter: gl.NEAREST,
//       min_filter: gl.LINEAR,
//
//       // Defaults to gl.CLAMP_TO_EDGE, set both at once with "wrap"
//       wrap_s: gl.REPEAT,
//       wrap_t: gl.REPEAT,
//
//       format: gl.RGB, // Defaults to gl.RGBA
//       type: gl.FLOAT  // Defaults to gl.UNSIGNED_BYTE
//     });

define('models/energy2d/gpu/texture',['require','models/energy2d/gpu/context'],function (require) {
  'use strict';
  var
    // Dependencies.
    context = require('models/energy2d/gpu/context'),

    // WebGL context.
    gl,

    // Class to be exported.
    Texture;

  Texture = function (width, height, options) {
    gl = context.getWebGLContext();
    options = options || {};
    // Basic texture params.
    this.id = gl.createTexture();
    this.width = width;
    this.height = height;
    this.format = options.format || gl.RGBA;
    this.type = options.type || gl.UNSIGNED_BYTE;
    // Number of texture unit which contains this texture (if any).
    this.tex_unit = null;
    // Render target params.
    this.fbo = null;

    // Set parameters.
    gl.bindTexture(gl.TEXTURE_2D, this.id);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, options.mag_filter || options.filter || gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, options.min_filter || options.filter || gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.wrap || options.wrap_s || gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.wrap || options.wrap_t || gl.CLAMP_TO_EDGE);
    gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, null);
  };

  // Set texture as render target.
  // After this call user can render to texture.
  Texture.prototype.setAsRenderTarget = function () {
    if (this.fbo === null) {
      // FBO initialization during first call.
      this.fbo = gl.createFramebuffer();
      gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.id, 0);
      gl.viewport(0, 0, this.width, this.height);
    } else {
      gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
      gl.viewport(0, 0, this.width, this.height);
    }
  };

  // Bind this texture to the given texture unit (0-7, defaults to 0).
  Texture.prototype.bind = function (unit) {
    this.tex_unit = unit || 0;
    gl.activeTexture(gl.TEXTURE0 + this.tex_unit);
    gl.bindTexture(gl.TEXTURE_2D, this.id);
  };

  // Unbind this texture.
  Texture.prototype.unbind = function (unit) {
    if (this.tex_unit === null) {
      return;
    }
    gl.activeTexture(gl.TEXTURE0 + this.tex_unit);
    gl.bindTexture(gl.TEXTURE_2D, null);
    this.tex_unit = null;
  };

  // Render all draw calls in `callback` to this texture. It also temporarily
  // changes the viewport to the size of the texture.
  Texture.prototype.drawTo = function (callback) {
    if (this.fbo === null) {
      throw new Error("Texture: call setupAsRenderTarget() method first.");
    }
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
    gl.viewport(0, 0, this.width, this.height);

    callback();

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  };

  // Switch this texture with 'other', useful for the ping-pong rendering
  // technique used in multi-stage rendering.
  // Textures should have identical dimensions, types and in general - parameters.
  // Only ID, FBO and active texture unit values are swapped.
  Texture.prototype.swapWith = function (other) {
    var temp;
    // Swap ID.
    temp = other.id;
    other.id = this.id;
    this.id = temp;
    // Swap active texture unit.
    temp = other.tex_unit;
    other.tex_unit = this.tex_unit;
    this.tex_unit = temp;
    // Swap FBO.
    temp = other.fbo;
    other.fbo = this.fbo;
    this.fbo = temp;
  };

  // Export constructor function.
  return Texture;
});

/*jslint indent: 2, browser: true, newcap: true, es5: true */
/*globals define: false, Float32Array: false, Uint16Array: false*/

// Represents a collection of vertex buffers and index buffers. Each vertex
// buffer maps to one attribute in GLSL and has a corresponding property set
// on the Mesh instance. There is one vertex buffer by default: `vertices`,
// which maps to `gl_Vertex`. The `coords`, `normals`, and `colors` vertex
// buffers map to `gl_TexCoord`, `gl_Normal`, and `gl_Color` respectively,
// and can be enabled by setting the corresponding options to true. There are
// two index buffers, `triangles` and `lines`, which are used for rendering
// `gl.TRIANGLES` and `gl.LINES`, respectively. Only `triangles` is enabled by
// default, although `computeWireframe()` will add a normal buffer if it wasn't
// initially enabled.

define('models/energy2d/gpu/mesh',['require','models/energy2d/gpu/context'],function (require) {
  'use strict';
  var
    // Dependencies.
    context = require('models/energy2d/gpu/context'),

    // The internal `gl` variable holds the current WebGL context.
    gl,

    // Internal, private class.
    Buffer,
    // Class to be exported.
    Mesh;

  // Provides a simple method of uploading data to a GPU buffer. Example usage:
  //
  //     var vertices = new GL.Buffer(gl.ARRAY_BUFFER, Float32Array);
  //     var indices = new GL.Buffer(gl.ELEMENT_ARRAY_BUFFER, Uint16Array);
  //     vertices.data = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]];
  //     indices.data = [[0, 1, 2], [2, 1, 3]];
  //     vertices.compile();
  //     indices.compile();
  Buffer = function (target, type) {
    gl = context.getWebGLContext();
    this.buffer = null;
    this.target = target;
    this.type = type;
    this.data = [];
  };

  // Upload the contents of `data` to the GPU in preparation for rendering. The
  // data must be a list of lists where each inner list has the same length. For
  // example, each element of data for vertex normals would be a list of length three.
  // This will remember the data length and element length for later use by shaders.
  // The type can be either `gl.STATIC_DRAW` or `gl.DYNAMIC_DRAW`, and defaults to
  // `gl.STATIC_DRAW`.
  //
  // This could have used `[].concat.apply([], this.data)` to flatten
  // the array but Google Chrome has a maximum number of arguments so the
  // concatenations are chunked to avoid that limit.
  Buffer.prototype.compile = function (type) {
    var data = [], i, chunk, spacing;
    for (i = 0, chunk = 10000; i < this.data.length; i += chunk) {
      data = Array.prototype.concat.apply(data, this.data.slice(i, i + chunk));
    }
    spacing = this.data.length ? data.length / this.data.length : 0;
    if (spacing !== Math.round(spacing)) {
      throw new Error('Mesh: buffer elements not of consistent size, average size is ' + spacing);
    }
    this.buffer = this.buffer || gl.createBuffer();
    this.buffer.length = data.length;
    this.buffer.spacing = spacing;
    gl.bindBuffer(this.target, this.buffer);
    gl.bufferData(this.target, new this.type(data), type || gl.STATIC_DRAW);
  };

  Mesh = function (options) {
    gl = context.getWebGLContext();
    options = options || {};
    this.vertexBuffers = {};
    this.indexBuffers = {};
    this.addVertexBuffer('vertices', 'gl_Vertex');
    if (options.coords) {
      this.addVertexBuffer('coords', 'gl_TexCoord');
    }
    if (options.normals) {
      this.addVertexBuffer('normals', 'gl_Normal');
    }
    if (options.colors) {
      this.addVertexBuffer('colors', 'gl_Color');
    }
    if (options.lines === undefined || options.triangles) {
      this.addIndexBuffer('triangles');
    }
    if (options.lines) {
      this.addIndexBuffer('lines');
    }
  };

  // Add a new vertex buffer with a list as a property called `name` on this object
  // and map it to the attribute called `attribute` in all shaders that draw this mesh.
  Mesh.prototype.addVertexBuffer = function (name, attribute) {
    var buffer = this.vertexBuffers[attribute] = new Buffer(gl.ARRAY_BUFFER, Float32Array);
    buffer.name = name;
    this[name] = [];
  };

  // Add a new index buffer with a list as a property called `name` on this object.
  Mesh.prototype.addIndexBuffer = function (name) {
    var buffer = this.indexBuffers[name] = new Buffer(gl.ELEMENT_ARRAY_BUFFER, Uint16Array);
    this[name] = [];
  };

  // Upload all attached buffers to the GPU in preparation for rendering. This
  // doesn't need to be called every frame, only needs to be done when the data
  // changes.
  Mesh.prototype.compile = function () {
    var attribute, name, buffer;
    for (attribute in this.vertexBuffers) {
      if (this.vertexBuffers.hasOwnProperty(attribute)) {
        buffer = this.vertexBuffers[attribute];
        buffer.data = this[buffer.name];
        buffer.compile();
      }
    }

    for (name in this.indexBuffers) {
      if (this.indexBuffers.hasOwnProperty(name)) {
        buffer = this.indexBuffers[name];
        buffer.data = this[name];
        buffer.compile();
      }
    }
  };

  // Generates a square 2x2 mesh the xy plane centered at the origin. The
  // `options` argument specifies options to pass to the mesh constructor.
  // Additional options include `detailX` and `detailY`, which set the tesselation
  // in x and y, and `detail`, which sets both `detailX` and `detailY` at once.
  // Two triangles are generated by default.
  // Example usage:
  //
  //     var mesh1 = GL.Mesh.plane();
  //     var mesh2 = GL.Mesh.plane({ detail: 5 });
  //     var mesh3 = GL.Mesh.plane({ detailX: 20, detailY: 40 });
  //
  Mesh.plane = function (options) {
    var mesh, detailX, detailY, x, y, t, s, i;
    options = options || {};
    mesh = new Mesh(options);
    detailX = options.detailX || options.detail || 1;
    detailY = options.detailY || options.detail || 1;

    for (y = 0; y <= detailY; y += 1) {
      t = y / detailY;
      for (x = 0; x <= detailX; x += 1) {
        s = x / detailX;
        mesh.vertices.push([2 * s - 1, 2 * t - 1, 0]);
        if (mesh.coords) {
          mesh.coords.push([s, t]);
        }
        if (mesh.normals) {
          mesh.normals.push([0, 0, 1]);
        }
        if (x < detailX && y < detailY) {
          i = x + y * (detailX + 1);
          mesh.triangles.push([i, i + 1, i + detailX + 1]);
          mesh.triangles.push([i + detailX + 1, i + 1, i + detailX + 2]);
        }
      }
    }

    mesh.compile();
    return mesh;
  };

  // Export constructor function.
  return Mesh;
});

/*jshint indent: 2, browser: true, newcap: true, multistr: true, es5: true */
/*global define: false, Float32Array: false, Uint8Array: false*/

// GPGPU Utils (singleton, one instance in the environment).
define('models/energy2d/gpu/gpgpu',['require','models/energy2d/gpu/context','models/energy2d/gpu/texture','models/energy2d/gpu/shader','models/energy2d/gpu/mesh'],function (require) {
  'use strict';
  var
    // Dependencies.
    context = require('models/energy2d/gpu/context'),
    Texture = require('models/energy2d/gpu/texture'),
    Shader  = require('models/energy2d/gpu/shader'),
    Mesh    = require('models/energy2d/gpu/mesh'),

    // The internal `gl` variable holds the current WebGL context.
    gl,

    // GPGPU utils must know dimensions of data (grid).
    // This assumption that all the textures will have the same dimensions is
    // caused by performance reasons (helps avoiding recreating data structures).
    // To set grid dimensions and initialize WebGL context, call init(grid_width, grid_height).
    grid_width,
    grid_height,

    // Texture used as a temporary storage (Float, RGBA).
    temp_texture,
    // Texture used for Float to RGBA conversion (Unsigned Byte, RGBA).
    output_texture,
    // Array (Float32Array) used as temporal storage during writing RGBA textures.
    temp_storage,
    // Mesh used for rendering.
    plane,

    // Flag which determines if synchronization is allowed or not.
    sync_allowed = false,

    // Flag which determines if WebGL context and necessary objects are initialized.
    WebGL_initialized = false,

    // Special shader for encoding floats based on:
    // https://github.com/cscheid/facet/blob/master/src/shade/bits/encode_float.js
    encode_program,
    copy_program,

    // GLSL sources.
    basic_vertex_shader =
    '\
    varying vec2 coord;\
    void main() {\
      coord = gl_Vertex.xy * 0.5 + 0.5;\
      gl_Position = vec4(gl_Vertex.xyz, 1.0);\
    }',

    encode_fragment_shader =
    '\
    uniform sampler2D texture;\
    uniform float channel;\
    varying vec2 coord;\
    float shift_right(float v, float amt) {\
      v = floor(v) + 0.5;\
      return floor(v / exp2(amt));\
    }\
    float shift_left(float v, float amt) {\
      return floor(v * exp2(amt) + 0.5);\
    }\
    \
    float mask_last(float v, float bits) {\
      return mod(v, shift_left(1.0, bits));\
    }\
    float extract_bits(float num, float from, float to) {\
      from = floor(from + 0.5);\
      to = floor(to + 0.5);\
      return mask_last(shift_right(num, from), to - from);\
    }\
    vec4 encode_float(float val) {\
      if (val == 0.0)\
        return vec4(0, 0, 0, 0);\
      float sign = val > 0.0 ? 0.0 : 1.0;\
      val = abs(val);\
      float exponent = floor(log2(val));\
      float biased_exponent = exponent + 127.0;\
      float fraction = ((val / exp2(exponent)) - 1.0) * 8388608.0;\
      \
      float t = biased_exponent / 2.0;\
      float last_bit_of_biased_exponent = fract(t) * 2.0;\
      float remaining_bits_of_biased_exponent = floor(t);\
      \
      float byte4 = extract_bits(fraction, 0.0, 8.0) / 255.0;\
      float byte3 = extract_bits(fraction, 8.0, 16.0) / 255.0;\
      float byte2 = (last_bit_of_biased_exponent * 128.0 + extract_bits(fraction, 16.0, 23.0)) / 255.0;\
      float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;\
      return vec4(byte4, byte3, byte2, byte1);\
    }\
    void main() {\
      vec4 data = texture2D(texture, coord);\
      if (channel == 0.0)\
        gl_FragColor = encode_float(data.r);\
      else if (channel == 1.0)\
        gl_FragColor = encode_float(data.g);\
      else if (channel == 2.0)\
        gl_FragColor = encode_float(data.b);\
      else\
        gl_FragColor = encode_float(data.a);\
    }',

    copy_fragment_shader =
    '\
    uniform sampler2D texture;\
    varying vec2 coord;\
    void main() {\
      gl_FragColor = texture2D(texture, coord);\
    }',

    // Common error messages.
    INIT_ERR = 'GPGPU: call init(grid_width, grid_height) with proper dimensions first!',

    // Features and extensions. Their availability will be updated during initialization.
    feature = {
      'WebGLContext': {
        required: true,
        available: false
      },
      'OES_texture_float': {
        required: true,
        available: false
      },
      'FLOAT texture as render target': {
        required: true,
        available: false
      },
      'OES_texture_float_linear': {
        required: false,
        available: false
      }
    },

    //
    // Private methods.
    //
    initWebGL = function () {
      // Setup WebGL context.
      gl = context.getWebGLContext();
      if (gl) {
        feature['WebGLContext'].available = true;
      } else {
        feature['WebGLContext'].available = false;
        throw new Error("GPGPU: WebGL is not supported!");
      }

      // Check if OES_texture_float is available.
      if (gl.getExtension('OES_texture_float')) {
        feature['OES_texture_float'].available = true;
      } else {
        feature['OES_texture_float'].available = false;
        throw new Error("GPGPU: OES_texture_float is not supported!");
      }

      // Optional extension check.
      if (gl.getExtension('OES_texture_float_linear')) {
        feature['OES_texture_float_linear'].available = true;
      } else {
        feature['OES_texture_float_linear'].available = false;
        console.warn("GPGPU: OES_texture_float_linear is not supported. Renering quality will be affected.");
      }

      // Check if rendering to FLOAT textures is supported.
      temp_texture = new Texture(1, 1, {
        type: gl.FLOAT,
        format: gl.RGBA,
        filter: feature['OES_texture_float_linear'].available ? gl.LINEAR : gl.NEAREST
      });
      temp_texture.setAsRenderTarget();
      if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
        feature['FLOAT texture as render target'].available = true;
      } else {
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        feature['FLOAT texture as render target'].available = false;
        throw new Error("GPGPU: FLOAT texture as render target is not supported!");
      }
      gl.bindFramebuffer(gl.FRAMEBUFFER, null);

      // Configure WebGL context and create necessary objects and structures.
      gl.disable(gl.DEPTH_TEST);
      plane = Mesh.plane();
      encode_program = new Shader(basic_vertex_shader, encode_fragment_shader);
      copy_program = new Shader(basic_vertex_shader, copy_fragment_shader);
      // Initialization successful.
      WebGL_initialized = true;
    },

    packRGBAData = function (R, G, B, A, storage) {
      var i, i4, len;

      if (R.length !== G.length || R.length !== B.length || R.length !== A.length ||
          storage.length !== R.length * 4) {
        throw new Error("GPGPU: Invalid input data length.");
      }
      for (i = 0, len = R.length; i < len; i += 1) {
        i4 = i * 4;
        storage[i4]     = R[i];
        storage[i4 + 1] = G[i];
        storage[i4 + 2] = B[i];
        storage[i4 + 3] = A[i];
      }
    };

  //
  // Public API.
  //
  return {
    // Setups rendering context (only during first call) and necessary storage (texture, array).
    init: function (width, height) {
      var filter;

      if (!WebGL_initialized) {
        initWebGL();
      }
      // Set dimensions.
      grid_width = width;
      grid_height = height;

      filter = feature['OES_texture_float_linear'].available ? gl.LINEAR : gl.NEAREST;

      // Setup storage for given dimensions.
      temp_texture   = new Texture(grid_width, grid_height, { type: gl.FLOAT, format: gl.RGBA, filter: filter });
      output_texture = new Texture(grid_width, grid_height, { type: gl.UNSIGNED_BYTE, format: gl.RGBA, filter: filter });
      temp_storage   = new Float32Array(grid_width * grid_height * 4);
    },

    get featuresInfo() {
      if (!WebGL_initialized) {
        try {
          // While testing features / extensions, we don't want to throw
          // exceptions.
          initWebGL();
        } catch (e) {}
      }
      return feature;
    },

    getWebGLContext: function () {
      if (gl === undefined) {
        initWebGL();
      }
      return gl;
    },

    // Creates a floating point texture with proper parameters.
    createTexture: function () {
      if (!grid_width || !grid_height) {
        return new Error(INIT_ERR);
      }
      // Use RGBA format as this is the safest option. Single channel textures aren't well supported
      // as render targets attached to FBO.
      return new Texture(grid_width, grid_height, {
        type: gl.FLOAT,
        format: gl.RGBA,
        filter: feature['OES_texture_float_linear'].available ? gl.LINEAR : gl.NEAREST
      });
    },

    // Convert given array to the RGBA FLoat32Array (which can be used
    // in the writeTexture function) and fill one of its channel.
    // Channel should be between 0 and 3, where 0 = R, 1 = G, 2 = B and 3 = A.
    convertToRGBA: function (data, channel, output) {
      var rgba, i, len, i4;

      if (data.length !== grid_width * grid_height) {
        throw new Error("GPGPU: Invalid input data length.");
      }

      if (output === undefined) {
        rgba = new Float32Array(data.length * 4);
      } else {
        rgba = output;
      }

      if (channel === undefined) {
        channel = 0;
      }

      // Fill RGBA array.
      for (i = 0, len = data.length; i < len; i += 1) {
        i4 = i * 4;
        rgba[i4] = rgba[i4 + 1] = rgba[i4 + 2] = rgba[i4 + 3] = 0;
        rgba[i4 + channel] = data[i];
      }

      return rgba;
    },

    // Write a texture.
    writeTexture: function (tex, input) {
      var rgba = this.convertToRGBA(input, 0, temp_storage);
      // Make sure that texture is bound.
      gl.bindTexture(gl.TEXTURE_2D, tex.id);
      gl.texImage2D(gl.TEXTURE_2D, 0, tex.format, tex.width, tex.height, 0, tex.format, tex.type, rgba);
    },

    writeRGBATexture: function (tex, R, G, B, A) {
      packRGBAData(R, G, B, A, temp_storage);
      // Make sure that texture is bound.
      gl.bindTexture(gl.TEXTURE_2D, tex.id);
      gl.texImage2D(gl.TEXTURE_2D, 0, tex.format, tex.width, tex.height, 0, tex.format, tex.type, temp_storage);
    },

    // Read a floating point texture.
    // Returns Float32Array.
    readTexture: function (tex, output, channel) {
      var output_storage;
      if (!gl || tex.width !== grid_width || tex.height !== grid_height) {
        return new Error(INIT_ERR);
      }
      if (channel === undefined) {
        channel = 0;
      }
      // Use buffer of provided ouput array. So, when result is written there,
      // output is automaticaly updated in a right way.
      output_storage = new Uint8Array(output.buffer);

      tex.bind();
      output_texture.setAsRenderTarget();
      encode_program.uniforms({ channel: channel });
      encode_program.draw(plane);
      // format: gl.RGBA, type: gl.UNSIGNED_BYTE - only this set is accepted by WebGL readPixels.
      gl.readPixels(0, 0, output_texture.width, output_texture.height, output_texture.format, output_texture.type, output_storage);
    },

    copyTexture: function (src_tex, dst_tex) {
      src_tex.bind();
      dst_tex.setAsRenderTarget();
      copy_program.draw(plane);
    },

    // Execute a GLSL program.
    // Arguments:
    // - program - GL.Shader
    // - textures - array of GL.Texture
    // - output - output texture
    executeProgram: function (program, textures, output) {
      var i, len;
      // Bind textures for reading.
      for (i = 0, len = textures.length; i < len; i += 1) {
        textures[i].bind(i);
      }
      // Use temp texture as writing and reading from the same texture is impossible.
      temp_texture.setAsRenderTarget();
      // Draw simple plane (coordinates x/y from -1 to 1 to cover whole viewport).
      program.draw(plane);
      // Unbind textures.
      for (i = 0, len = textures.length; i < len; i += 1) {
        textures[i].unbind(i);
      }
      output.swapWith(temp_texture);
    },

    // Synchronization can be useful for debugging.
    setSynchronizationAllowed: function (b) {
      sync_allowed = b;
    },

    // Block until all GL execution is complete if synchronization is allowed.
    tryFinish: function () {
      if (sync_allowed) {
        gl.finish();
      }
    }
  };
});


define('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/basic.vs.glsl',[],function () { return 'varying vec2 coord;\n\nvoid main() {\n  coord = gl_Vertex.xy * 0.5 + 0.5;\n  gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0);\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/solver.fs.glsl',[],function () { return '// texture 0:\n// - R: t\n// - G: t0\n// - B: tb\n// - A: conductivity\nuniform sampler2D data0_tex;\n// texture 1:\n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n\nuniform vec2 grid;\nuniform float hx;\nuniform float hy;\nuniform float inv_timeStep;\n\n// Boundary conditions uniforms\nuniform float enforce_temp;\nuniform float vN;\nuniform float vS;\nuniform float vW;\nuniform float vE;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data0 = texture2D(data0_tex, coord);\n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y) {\n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n    float tb = data0.b;\n    // Check if tb is NaN. isnan() function is not available\n    // in OpenGL ES GLSL, so use some tricks. IEEE 754 spec defines\n    // that NaN != NaN, however this seems to not work on Windows.\n    // So, also check if the value is outside [-3.4e38, 3.4e38] (3.4e38\n    // is close to 32Float max value), as such values are not expected.\n    if (tb != tb || tb < -3.4e38 || tb > 3.4e38) {\n      vec4 data1 = texture2D(data1_tex, coord);\n      vec4 data0_m_dy = texture2D(data0_tex, coord - dy);\n      vec4 data0_p_dy = texture2D(data0_tex, coord + dy);\n      vec4 data0_m_dx = texture2D(data0_tex, coord - dx);\n      vec4 data0_p_dx = texture2D(data0_tex, coord + dx);\n      float sij = data1.g * data1.b * inv_timeStep;\n      float rij = data0.a;\n      float axij = hx * (rij + data0_m_dy.a);\n      float bxij = hx * (rij + data0_p_dy.a);\n      float ayij = hy * (rij + data0_m_dx.a);\n      float byij = hy * (rij + data0_p_dx.a);\n      data0.r = (data0.g * sij + data1.r\n                 + axij * data0_m_dy.r\n                 + bxij * data0_p_dy.r\n                 + ayij * data0_m_dx.r\n                 + byij * data0_p_dx.r)\n                 / (sij + axij + bxij + ayij + byij);\n    } else {\n      data0.r = tb;\n    }\n  } else if (enforce_temp == 1.0) {\n    // "temperature at border" boundary conditions are\n    // integrated into this shader.\n    if (coord.x < grid.x) {\n      data0.r = vN;\n    } else if (coord.x > 1.0 - grid.x) {\n      data0.r = vS;\n    } else if (coord.y < grid.y) {\n      data0.r = vW;\n    } else if (coord.y > 1.0 - grid.y) {\n      data0.r = vE;\n    }\n  }\n  gl_FragColor = data0;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/force-flux-t.fs.glsl',[],function () { return '// texture 0: \n// - R: t\n// - G: t0\n// - B: tb\n// - A: conductivity\nuniform sampler2D data0_tex;\n\nuniform vec2 grid;\nuniform float vN;\nuniform float vS;\nuniform float vW;\nuniform float vE;\nuniform float delta_x;\nuniform float delta_y;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data0 = texture2D(data0_tex, coord);\n  vec2 dx = vec2(grid.x, 0.0);\n  vec2 dy = vec2(0.0, grid.y);\n  if (coord.x < grid.x) {\n    data0.r = texture2D(data0_tex, coord + dx).r\n            + vN * delta_y / data0.a;\n  } else if (coord.x > 1.0 - grid.x) {\n    data0.r = texture2D(data0_tex, coord - dx).r\n            - vS * delta_y / data0.a;\n  } else if (coord.y < grid.y) {\n    data0.r = texture2D(data0_tex, coord + dy).r\n            - vW * delta_x / data0.a;\n  } else if (coord.y > 1.0 - grid.y) {\n    data0.r = texture2D(data0_tex, coord - dy).r\n            + vE * delta_x / data0.a;\n  }\n  gl_FragColor = data0;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/t-to-t0.fs.glsl',[],function () { return '// texture 0: \n// - R: t\n// - G: t0\n// - B: tb\n// - A: conductivity\nuniform sampler2D data0_tex;\n\nvarying vec2 coord;\n\nvoid main() {\n\tvec4 data0 = texture2D(data0_tex, coord);\n\tdata0.g = data0.r;\n\tgl_FragColor = data0;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/maccormack-step1.fs.glsl',[],function () { return '// texture 0: \n// - R: t\n// - G: t0\n// - B: tb\n// - A: conductivity\nuniform sampler2D data0_tex;\n// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\nuniform float tx;\nuniform float ty;\n\n// Boundary conditions uniforms.\nuniform float enforce_temp;\nuniform float vN;\nuniform float vS;\nuniform float vW;\nuniform float vE;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data0 = texture2D(data0_tex, coord);\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y) {\n    \n    float fluidity = texture2D(data1_tex, coord).a;\n    if (fluidity == 1.0) {\n      vec2 dx = vec2(grid.x, 0.0);\n      vec2 dy = vec2(0.0, grid.y);\n\n      // Temperature.\n      float t_m_dy = texture2D(data0_tex, coord - dy).r;\n      float t_p_dy = texture2D(data0_tex, coord + dy).r;\n      float t_m_dx = texture2D(data0_tex, coord - dx).r;\n      float t_p_dx = texture2D(data0_tex, coord + dx).r;\n      // Velocity.\n      float u_m_dy = texture2D(data2_tex, coord - dy).r;\n      float u_p_dy = texture2D(data2_tex, coord + dy).r;\n      float v_m_dx = texture2D(data2_tex, coord - dx).g;\n      float v_p_dx = texture2D(data2_tex, coord + dx).g;\n      // Update T0.\n      data0.g = data0.r - tx * (u_p_dy * t_p_dy - u_m_dy * t_m_dy)\n                        - ty * (v_p_dx * t_p_dx - v_m_dx * t_m_dx);\n    }\n  } else if (enforce_temp == 1.0) {\n    // "temperature at border" boundary conditions are\n    // integrated into this shader.\n    if (coord.x < grid.x) {\n      data0.g = vN;\n    } else if (coord.x > 1.0 - grid.x) {\n      data0.g = vS;\n    } else if (coord.y < grid.y) {\n      data0.g = vW;\n    } else if (coord.y > 1.0 - grid.y) {\n      data0.g = vE;\n    }\n  }\n  gl_FragColor = data0;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/maccormack-step2.fs.glsl',[],function () { return '// texture 0: \n// - R: t\n// - G: t0\n// - B: tb\n// - A: conductivity\nuniform sampler2D data0_tex;\n// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\nuniform float tx;\nuniform float ty;\n\n// Boundary conditions uniforms.\nuniform float enforce_temp;\nuniform float vN;\nuniform float vS;\nuniform float vW;\nuniform float vE;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data0 = texture2D(data0_tex, coord);\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y) {\n    \n    float fluidity = texture2D(data1_tex, coord).a;\n    if (fluidity == 1.0) {\n      vec2 dx = vec2(grid.x, 0.0);\n      vec2 dy = vec2(0.0, grid.y);\n\n      // Temperature t0.\n      float t0_m_dy = texture2D(data0_tex, coord - dy).g;\n      float t0_p_dy = texture2D(data0_tex, coord + dy).g;\n      float t0_m_dx = texture2D(data0_tex, coord - dx).g;\n      float t0_p_dx = texture2D(data0_tex, coord + dx).g;\n      // Velocity.\n      float u = texture2D(data2_tex, coord).r;\n      float v = texture2D(data2_tex, coord).g;\n      // Update T.\n      data0.r = 0.5 * (data0.r + data0.g)\n              - 0.5 * tx * u * (t0_p_dy - t0_m_dy)\n              - 0.5 * ty * v * (t0_p_dx - t0_m_dx);\n    }\n  } else if (enforce_temp == 1.0) {\n    // "temperature at border" boundary conditions are\n    // integrated into this shader.\n    if (coord.x < grid.x) {\n      data0.r = vN;\n    } else if (coord.x > 1.0 - grid.x) {\n      data0.r = vS;\n    } else if (coord.y < grid.y) {\n      data0.r = vW;\n    } else if (coord.y > 1.0 - grid.y) {\n      data0.r = vE;\n    }\n  }\n  gl_FragColor = data0;\n}\n';});

/*global define: false*/

define('models/energy2d/models/physics-solvers-gpu/heat-solver-gpu',['require','exports','module','models/energy2d/gpu/shader','models/energy2d/gpu/gpgpu','text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/basic.vs.glsl','text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/solver.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/force-flux-t.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/force-flux-t.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/t-to-t0.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/maccormack-step1.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/maccormack-step2.fs.glsl'],function (require, exports) {
  'use strict';
  var
    // Dependencies.
    Shader = require('models/energy2d/gpu/shader'),
    // GPGPU utilities. It's a singleton instance.
    // It should have been previously initialized by core-model.
    gpgpu  = require('models/energy2d/gpu/gpgpu'),
    // Shader sources.
    basic_vs            = require('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/basic.vs.glsl'),
    solver_fs           = require('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/solver.fs.glsl'),
    force_flux_t_fs     = require('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/force-flux-t.fs.glsl'),
    force_flux_t0_fs    = require('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/force-flux-t.fs.glsl'),
    t_to_t0             = require('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/t-to-t0.fs.glsl'),
    maccormack_step1_fs = require('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/maccormack-step1.fs.glsl'),
    maccormack_step2_fs = require('text!models/energy2d/models/physics-solvers-gpu/heat-solver-glsl/maccormack-step2.fs.glsl'),

    RELAXATION_STEPS = 10;

  exports.makeHeatSolverGPU = function (model) {
    var
      // ========================================================================
      // GLSL Shaders:
      // - Main solver.
      solver_program           = new Shader(basic_vs, solver_fs),
      // - Force flux boundary (for T).
      force_flux_t_program     = new Shader(basic_vs, force_flux_t_fs),
      // - Force flux boundary (for T0).
      force_flux_t0_program    = new Shader(basic_vs, force_flux_t0_fs),
      // - Copy single channel of texture (t to t0).
      t_to_t0_program          = new Shader(basic_vs, t_to_t0),
      // - MacCormack advection step 1.
      maccormack_step1_program = new Shader(basic_vs, maccormack_step1_fs),
      // - MacCormack advection step 2.
      maccormack_step2_program = new Shader(basic_vs, maccormack_step2_fs),
      // ========================================================================

      // Basic simulation parameters.
      props = model.getModelOptions(),
      nx = props.grid_width,
      ny = props.grid_height,

      timeStep = props.timeStep,
      boundary = props.boundary,

      delta_x = props.model_width / props.grid_width,
      delta_y = props.model_height / props.grid_height,

      relaxation_steps = RELAXATION_STEPS,

      // Simulation textures provided by model.
      // texture 0:
      // - R: t
      // - G: t0
      // - B: tb
      // - A: conductivity
      data0_tex = model.getSimulationTexture(0),
      // texture 1:
      // - R: q
      // - G: capacity
      // - B: density
      // - A: fluidity
      data1_tex = model.getSimulationTexture(1),
      // texture 2:
      // - R: u
      // - G: v
      // - B: u0
      // - A: v0
      data2_tex = model.getSimulationTexture(2),

      // Convenience variables.
      data_0_1_2_array = [data0_tex, data1_tex, data2_tex],
      data_0_1_array = [data0_tex, data1_tex],
      data_0_array = [data0_tex],
      grid_vec = [1 / ny, 1 / nx],

      init = function () {
        var uniforms;

        // Solver program uniforms.
        uniforms = {
          // Texture units.
          data0_tex: 0,
          data1_tex: 1,
          // Uniforms.
          grid: grid_vec,
          enforce_temp: 0.0,
          hx: 0.5 / (delta_x * delta_x),
          hy: 0.5 / (delta_y * delta_y),
          inv_timeStep: 1.0 / timeStep
        };
        solver_program.uniforms(uniforms);

        // MacCormack step 1 program uniforms.
        uniforms = {
          // Texture units.
          data0_tex: 0,
          data1_tex: 1,
          data2_tex: 2,
          // Uniforms.
          grid: grid_vec,
          enforce_temp: 0.0,
          tx: 0.5 * timeStep / delta_x,
          ty: 0.5 * timeStep / delta_y
        };
        maccormack_step1_program.uniforms(uniforms);
        maccormack_step2_program.uniforms(uniforms);

        if (boundary.type === "temperature") {
          uniforms = {
            // Additional uniforms.
            enforce_temp: 1.0,
            vN:  boundary.upper,
            vS:  boundary.lower,
            vW:  boundary.left,
            vE:  boundary.right
          };
          // Integrate boundary conditions with other programs.
          // This is optimization that allows to limit render-to-texture calls.
          solver_program.uniforms(uniforms);
          maccormack_step1_program.uniforms(uniforms);
          maccormack_step2_program.uniforms(uniforms);
        } else if (boundary.type === "flux") {
          uniforms = {
            // Texture units.
            data0_tex: 0,
            // Uniforms.
            grid: grid_vec,
            vN: boundary.upper,
            vS: boundary.lower,
            vW: boundary.left,
            vE: boundary.right,
            delta_x: delta_x,
            delta_y: delta_y
          };
          // Flux boundary conditions can't be integrated into solver program,
          // so use separate GLSL programs.
          force_flux_t_program.uniforms(uniforms);
          force_flux_t0_program.uniforms(uniforms);
        }
      },

      macCormack = function () {
        // MacCormack step 1.
        gpgpu.executeProgram(
          maccormack_step1_program,
          data_0_1_2_array,
          data0_tex
        );
        if (boundary.type === "flux") {
          // Additional program for boundary conditions
          // is required only for "flux at border" option.
          // If "temperature at border" is used, boundary
          // conditions are enforced by the MacCormack program.
          gpgpu.executeProgram(
            force_flux_t0_program,
            data_0_array,
            data0_tex
          );
        }
        // MacCormack step 2.
        gpgpu.executeProgram(
          maccormack_step2_program,
          data_0_1_2_array,
          data0_tex
        );
        if (boundary.type === "flux") {
          // Additional program for boundary conditions
          // is required only for "flux at border" option.
          // If "temperature at border" is used, boundary
          // conditions are enforced by the MacCormack program.
          gpgpu.executeProgram(
            force_flux_t_program,
            data_0_array,
            data0_tex
          );
        }
      },

      heat_solver_gpu = {
        solve: function (convective) {
          var k;
          // Store previous values of t in t0.
          gpgpu.executeProgram(
            t_to_t0_program,
            data_0_array,
            data0_tex
          );
          for (k = 0; k < relaxation_steps; k += 1) {
            gpgpu.executeProgram(
              solver_program,
              data_0_1_array,
              data0_tex
            );
            if (boundary.type === "flux") {
              // Additional program for boundary conditions
              // is required only for "flux at border" option.
              // If "temperature at border" is used, boundary
              // conditions are enforced by the solver program.
              gpgpu.executeProgram(
                force_flux_t_program,
                data_0_array,
                data0_tex
              );
            }
          }
          if (convective) {
            macCormack();
          }
        }
      };
    // One-off initialization.
    init();
    return heat_solver_gpu;
  };
});

/*global define: false*/

define('models/energy2d/models/physics-solvers/fluid-solver',['require','exports','module','arrays'],function (require, exports) {
  'use strict';
  var
    arrays = require('arrays'),

    RELAXATION_STEPS = 5,
    GRAVITY = 0,

    BUOYANCY_AVERAGE_ALL = 0,
    BUOYANCY_AVERAGE_COLUMN = 1;

  exports.makeFluidSolver = function (model) {
    var
      // Basic simulation parameters.
      props = model.getModelOptions(),
      nx = props.grid_width,
      ny = props.grid_height,

      relaxationSteps = RELAXATION_STEPS,
      gravity = GRAVITY,

      // Simulation arrays provided by model.
      t        = model.getTemperatureArray(),
      fluidity = model.getFluidityArray(),
      uWind    = model.getUWindArray(),
      vWind    = model.getVWindArray(),

      // Internal simulation arrays.
      array_type = model.getArrayType(),
      u0         = arrays.create(nx * ny, 0, array_type),
      v0         = arrays.create(nx * ny, 0, array_type),

      // Convenience variables.
      nx1 = nx - 1,
      ny1 = ny - 1,
      nx2 = nx - 2,
      ny2 = ny - 2,

      deltaX = props.model_width / props.grid_width,
      deltaY = props.model_height / props.grid_height,

      i2dx  = 0.5 / deltaX,
      i2dy  = 0.5 / deltaY,
      idxsq = 1.0 / (deltaX * deltaX),
      idysq = 1.0 / (deltaY * deltaY),

      //
      // Private methods
      //

      // b = 1 horizontal; b = 2 vertical
      applyBoundary = function (b, f) {
        var
          horizontal = b === 1,
          vertical   = b === 2,
          nx1nx = nx1 * nx,
          nx2nx = nx2 * nx,
          i, j, inx, inx_plus1, inx_plus_ny1, inx_plus_ny2, nx_plusj;

        for (i = 1; i < nx1; i += 1) {
          inx = i * nx;
          inx_plus1 = inx + 1;
          inx_plus_ny1 = inx + ny1;
          inx_plus_ny2 = inx + ny2;
          // upper side
          f[inx] = vertical ? -f[inx_plus1] : f[inx_plus1];
          // lower side
          f[inx_plus_ny1] = vertical ? -f[inx_plus_ny2] : f[inx_plus_ny2];
        }
        for (j = 1; j < ny1; j += 1) {
          // left side
          nx_plusj = nx + j;
          f[j] = horizontal ? -f[nx_plusj] : f[nx_plusj];
          // right side
          f[nx1nx + j] = horizontal ? -f[nx2nx + j] : f[nx2nx + j];
        }

        // upper-left corner
        f[0] = 0.5 * (f[nx] + f[1]);
        // upper-right corner
        f[nx1nx] = 0.5 * (f[nx2nx] + f[nx1nx + 1]);
        // lower-left corner
        f[ny1] = 0.5 * (f[nx + ny1] + f[ny2]);
        // lower-right corner
        f[nx1nx + ny1] = 0.5 * (f[nx2nx + ny1] + f[nx1nx + ny2]);
      },

      setObstacleVelocity = function (u, v) {
        var
          count = 0,
          uw, vw,
          i, j, inx, jinx, jinx_plus_nx, jinx_minus_nx, jinx_plus_1, jinx_minus_1;

        for (i = 1; i < nx1; i += 1) {
          inx = i * nx;
          for (j = 1; j < ny1; j += 1) {
            jinx = inx + j;
            jinx_minus_nx = jinx - nx;
            jinx_plus_nx = jinx + nx;
            jinx_minus_1 = jinx - 1;
            jinx_plus_1 = jinx + 1;

            if (!fluidity[jinx]) {
              uw = uWind[jinx];
              vw = vWind[jinx];
              count = 0;
              if (fluidity[jinx_minus_nx]) {
                count += 1;
                u[jinx] = uw - u[jinx_minus_nx];
                v[jinx] = vw + v[jinx_minus_nx];
              } else if (fluidity[jinx_plus_nx]) {
                count += 1;
                u[jinx] = uw - u[jinx_plus_nx];
                v[jinx] = vw + v[jinx_plus_nx];
              }
              if (fluidity[jinx_minus_1]) {
                count += 1;
                u[jinx] = uw + u[jinx_minus_1];
                v[jinx] = vw - v[jinx_minus_1];
              } else if (fluidity[jinx_plus_1]) {
                count += 1;
                u[jinx] = uw + u[jinx_plus_1];
                v[jinx] = vw - v[jinx_plus_1];
              }
              if (count === 0) {
                u[jinx] = uw;
                v[jinx] = vw;
              }
            }
          }
        }
      },

      // ensure dx/dn = 0 at the boundary (the Neumann boundary condition)
      // float[][] x
      setObstacleBoundary = function (x) {
        var i, j, inx, jinx, jinx_plus_nx, jinx_minus_nx, jinx_plus_1, jinx_minus_1;

        for (i = 1; i < nx1; i += 1) {
          inx = i * nx;
          for (j = 1; j < ny1; j += 1) {
            jinx = inx + j;
            if (!fluidity[jinx]) {
              jinx_minus_nx = jinx - nx;
              jinx_plus_nx = jinx + nx;
              jinx_minus_1 = jinx - 1;
              jinx_plus_1 = jinx + 1;

              if (fluidity[jinx_minus_nx]) {
                x[jinx] = x[jinx_minus_nx];
              } else if (fluidity[jinx_plus_nx]) {
                x[jinx] = x[jinx_plus_nx];
              }
              if (fluidity[jinx_minus_1]) {
                x[jinx] = x[jinx_minus_1];
              } else if (fluidity[jinx_plus_1]) {
                x[jinx] = x[jinx_plus_1];
              }
            }
          }
        }
      },

      getMeanTemperature = function (i, j) {
        var
          lowerBound = 0,
          upperBound = ny,
          t0 = 0,
          k, inx_plus_k;

          // search for the upper bound
        for (k = j - 1; k > 0; k -= 1) {
          inx_plus_k = i * nx + k;
          if (!fluidity[inx_plus_k]) {
            lowerBound = k;
            break;
          }
        }

        for (k = j + 1; k < ny; k += 1) {
          inx_plus_k = i * nx + k;
          if (!fluidity[inx_plus_k]) {
            upperBound = k;
            break;
          }
        }

        for (k = lowerBound; k < upperBound; k += 1) {
          inx_plus_k = i * nx + k;
          t0 += t[inx_plus_k];
        }
        return t0 / (upperBound - lowerBound);
      },

      applyBuoyancy = function (f) {
        var
          g = gravity * props.timeStep,
          b = props.thermal_buoyancy * props.timeStep,
          t0,
          i, j, inx, jinx;

        switch (props.buoyancy_approximation) {
        case BUOYANCY_AVERAGE_ALL:
          t0 = (function (array) {
            // Returns average value of an array.
            var
              acc = 0,
              length = array.length,
              i;
            for (i = 0; i < length; i += 1) {
              acc += array[i];
            }
            return acc / length;
          }(t)); // Call with the temperature array.
          for (i = 1; i < nx1; i += 1) {
            inx = i * nx;
            for (j = 1; j < ny1; j += 1) {
              jinx = inx + j;
              if (fluidity[jinx]) {
                f[jinx] += (g - b) * t[jinx] + b * t0;
              }
            }
          }
          break;
        case BUOYANCY_AVERAGE_COLUMN:
          for (i = 1; i < nx1; i += 1) {
            inx = i * nx;
            for (j = 1; j < ny1; j += 1) {
              jinx = inx + j;
              if (fluidity[jinx]) {
                t0 = getMeanTemperature(i, j);
                f[jinx] += (g - b) * t[jinx] + b * t0;
              }
            }
          }
          break;
        }
      },

      conserve = function (u, v, phi, div) {
        var
          s = 0.5 / (idxsq + idysq),
          k, i, j, inx, jinx, jinx_plus_nx, jinx_minus_nx, jinx_plus_1, jinx_minus_1;

        for (i = 1; i < nx1; i += 1) {
          inx = i * nx;
          for (j = 1; j < ny1; j += 1) {
            jinx = inx + j;
            if (fluidity[jinx]) {
              jinx_minus_nx = jinx - nx;
              jinx_plus_nx = jinx + nx;
              jinx_minus_1 = jinx - 1;
              jinx_plus_1 = jinx + 1;

              div[jinx] = (u[jinx_plus_nx] - u[jinx_minus_nx]) * i2dx + (v[jinx_plus_1] - v[jinx_minus_1]) * i2dy;
              phi[jinx] = 0;
            }
          }
        }
        applyBoundary(0, div);
        applyBoundary(0, phi);
        setObstacleBoundary(div);
        setObstacleBoundary(phi);

        for (k = 0; k < relaxationSteps; k += 1) {
          for (i = 1; i < nx1; i += 1) {
            inx = i * nx;
            for (j = 1; j < ny1; j += 1) {
              jinx = inx + j;
              if (fluidity[jinx]) {
                jinx_minus_nx = jinx - nx;
                jinx_plus_nx = jinx + nx;
                jinx_minus_1 = jinx - 1;
                jinx_plus_1 = jinx + 1;

                phi[jinx] = s
                    * ((phi[jinx_minus_nx] + phi[jinx_plus_nx]) * idxsq
                    + (phi[jinx_minus_1] + phi[jinx_plus_1]) * idysq - div[jinx]);
              }
            }
          }
        }

        for (i = 1; i < nx1; i += 1) {
          inx = i * nx;
          for (j = 1; j < ny1; j += 1) {
            jinx = inx + j;
            if (fluidity[jinx]) {
              jinx_minus_nx = jinx - nx;
              jinx_plus_nx = jinx + nx;
              jinx_minus_1 = jinx - 1;
              jinx_plus_1 = jinx + 1;

              u[jinx] -= (phi[jinx_plus_nx] - phi[jinx_minus_nx]) * i2dx;
              v[jinx] -= (phi[jinx_plus_1] - phi[jinx_minus_1]) * i2dy;
            }
          }
        }
        applyBoundary(1, u);
        applyBoundary(2, v);
      },

      diffuse = function (b, f0, f) {
        var
          timeStep = props.timeStep,
          viscosity = props.background_viscosity,
          hx = timeStep * viscosity * idxsq,
          hy = timeStep * viscosity * idysq,
          dn = 1.0 / (1 + 2 * (hx + hy)),
          k, i, j, inx, jinx, jinx_plus_nx, jinx_minus_nx, jinx_plus_1, jinx_minus_1;

        arrays.copy(f, f0);
        for (k = 0; k < relaxationSteps; k += 1) {
          for (i = 1; i < nx1; i += 1) {
            inx = i * nx;
            for (j = 1; j < ny1; j += 1) {
              jinx = inx + j;
              if (fluidity[jinx]) {
                jinx_minus_nx = jinx - nx;
                jinx_plus_nx = jinx + nx;
                jinx_minus_1 = jinx - 1;
                jinx_plus_1 = jinx + 1;

                f[jinx] = (f0[jinx] + hx * (f[jinx_minus_nx] + f[jinx_plus_nx]) + hy
                        * (f[jinx_minus_1] + f[jinx_plus_1]))
                        * dn;
              }
            }
          }
          applyBoundary(b, f);
        }
      },

      // MacCormack
      macCormack = function (b, f0, f) {
        var
          timeStep = props.timeStep,
          tx = 0.5 * timeStep / deltaX,
          ty = 0.5 * timeStep / deltaY,
          i, j, inx, jinx, jinx_plus_nx, jinx_minus_nx, jinx_plus_1, jinx_minus_1;

        for (i = 1; i < nx1; i += 1) {
          inx = i * nx;
          for (j = 1; j < ny1; j += 1) {
            jinx = inx + j;
            if (fluidity[jinx]) {
              jinx_minus_nx = jinx - nx;
              jinx_plus_nx = jinx + nx;
              jinx_minus_1 = jinx - 1;
              jinx_plus_1 = jinx + 1;

              f[jinx] = f0[jinx]
                      - tx
                      * (u0[jinx_plus_nx] * f0[jinx_plus_nx] - u0[jinx_minus_nx]
                              * f0[jinx_minus_nx])
                      - ty
                      * (v0[jinx_plus_1] * f0[jinx_plus_1] - v0[jinx_minus_1]
                              * f0[jinx_minus_1]);
            }
          }
        }

        applyBoundary(b, f);

        for (i = 1; i < nx1; i += 1) {
          inx = i * nx;
          for (j = 1; j < ny1; j += 1) {
            jinx = inx + j;
            if (fluidity[jinx]) {
              jinx_minus_nx = jinx - nx;
              jinx_plus_nx = jinx + nx;
              jinx_minus_1 = jinx - 1;
              jinx_plus_1 = jinx + 1;

              f0[jinx] = 0.5 * (f0[jinx] + f[jinx]) - 0.5 * tx
                      * u0[jinx] * (f[jinx_plus_nx] - f[jinx_minus_nx]) - 0.5
                      * ty * v0[jinx] * (f[jinx_plus_1] - f[jinx_minus_1]);
            }
          }
        }

        arrays.copy(f0, f);

        applyBoundary(b, f);
      },

      advect = function (b, f0, f) {
        macCormack(b, f0, f);
      };

    return {
      // TODO: swap the two arrays instead of copying them every time?
      solve: function (u, v) {
        if (props.thermal_buoyancy !== 0) {
          applyBuoyancy(v);
        }
        setObstacleVelocity(u, v);
        if (props.background_viscosity > 0) {
          // inviscid case
          diffuse(1, u0, u);
          diffuse(2, v0, v);
          conserve(u, v, u0, v0);
          setObstacleVelocity(u, v);
        }
        arrays.copy(u, u0);
        arrays.copy(v, v0);
        advect(1, u0, u);
        advect(2, v0, v);
        conserve(u, v, u0, v0);
        setObstacleVelocity(u, v);
      }
    };
  };
});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/basic.vs.glsl',[],function () { return 'varying vec2 coord;\n\nvoid main() {\n  coord = gl_Vertex.xy * 0.5 + 0.5;\n  gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0);\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/maccormack-step1.fs.glsl',[],function () { return '// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\nuniform float tx;\nuniform float ty;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  float fluidity = texture2D(data1_tex, coord).a;\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y &&\n      fluidity == 1.0) {\n    \n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n    \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    // Update velocity UV components.\n    data2.rg = data2.ba - tx * (data2_p_dy.bb * data2_p_dy.ba - data2_m_dy.bb * data2_m_dy.ba)\n              - ty * (data2_p_dx.aa * data2_p_dx.ba - data2_m_dx.aa * data2_m_dx.ba);\n  }\n\n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/maccormack-step2.fs.glsl',[],function () { return '// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\nuniform float tx;\nuniform float ty;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  float fluidity = texture2D(data1_tex, coord).a;\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y &&\n      fluidity == 1.0) {\n    \n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n    \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    // Update velocity UV components.\n    data2.rg = 0.5 * (data2.ba + data2.rg) \n            - 0.5 * tx * data2.bb * (data2_p_dy.rg - data2_m_dy.rg)\n            - 0.5 * ty * data2.aa * (data2_p_dx.rg - data2_m_dx.rg);\n  }\n\n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/apply-uv-boundary.fs.glsl',[],function () { return '// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  vec2 dx = vec2(grid.x, 0.0);\n  vec2 dy = vec2(0.0, grid.y);\n  // Process corners.\n  // TODO: values from previous step are used for corners.\n  if (coord.x < grid.x && coord.y < grid.y) {  \n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    data2.rg = 0.5 * (data2_p_dy.rg + data2_p_dx.rg);\n  }\n  else if (coord.x > 1.0 - grid.x && coord.y < grid.y) {  \n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    data2.rg = 0.5 * (data2_p_dy.rg + data2_m_dx.rg);\n  }\n  else if (coord.x > 1.0 - grid.x && coord.y > 1.0 - grid.y) {  \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    data2.rg = 0.5 * (data2_m_dy.rg + data2_m_dx.rg);\n  }\n  else if (coord.x < grid.x && coord.y > 1.0 - grid.y) {  \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    data2.rg = 0.5 * (data2_m_dy.rg + data2_p_dx.rg);\n  }\n  // Process boundaries.\n  // Left.\n  else if (coord.x < grid.x) {\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    data2.rg = vec2(data2_p_dx.r, -data2_p_dx.g);\n  }\n  // Right.\n  else if (coord.x > 1.0 - grid.x) {\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    data2.rg = vec2(data2_m_dx.r, -data2_m_dx.g);\n  }\n  // Down.\n  else if (coord.y < grid.y) {\n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    data2.rg = vec2(-data2_p_dy.r, data2_p_dy.g);\n  }\n  // Up.\n  else if (coord.y > 1.0 - grid.y) {\n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    data2.rg = vec2(-data2_m_dy.r, data2_m_dy.g);\n  }\n  \n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/apply-u0v0-boundary.fs.glsl',[],function () { return '// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  vec2 dx = vec2(grid.x, 0.0);\n  vec2 dy = vec2(0.0, grid.y);\n  // Process corners.\n  // TODO: values from previous step are used for corners.\n  if (coord.x < grid.x && coord.y < grid.y) {  \n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    data2.ba = 0.5 * (data2_p_dy.ba + data2_p_dx.ba);\n  }\n  else if (coord.x > 1.0 - grid.x && coord.y < grid.y) {  \n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    data2.ba = 0.5 * (data2_p_dy.ba + data2_m_dx.ba);\n  }\n  else if (coord.x > 1.0 - grid.x && coord.y > 1.0 - grid.y) {  \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    data2.ba = 0.5 * (data2_m_dy.ba + data2_m_dx.ba);\n  }\n  else if (coord.x < grid.x && coord.y > 1.0 - grid.y) {  \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    data2.ba = 0.5 * (data2_m_dy.ba + data2_p_dx.ba);\n  }\n  // Process boundaries.\n  // Left.\n  else if (coord.x < grid.x) {\n    data2.ba = texture2D(data2_tex, coord + dx).ba;\n  }\n  // Right.\n  else if (coord.x > 1.0 - grid.x) {\n    data2.ba = texture2D(data2_tex, coord - dx).ba;\n  }\n  // Down.\n  else if (coord.y < grid.y) {\n    data2.ba = texture2D(data2_tex, coord + dy).ba;\n  }\n  // Up.\n  else if (coord.y > 1.0 - grid.y) {\n    data2.ba = texture2D(data2_tex, coord - dy).ba;\n  }\n  \n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/set-obstacle-boundary.fs.glsl',[],function () { return '// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  float fluidity = texture2D(data1_tex, coord).a;\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y &&\n      fluidity == 0.0) {\n    \n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n\n    if (texture2D(data1_tex, coord - dy).a == 1.0) {\n      data2.ba = texture2D(data2_tex, coord - dy).ba;\n    } \n    else if (texture2D(data1_tex, coord + dy).a == 1.0) {\n      data2.ba = texture2D(data2_tex, coord + dy).ba;\n    } \n\n    if (texture2D(data1_tex, coord - dx).a == 1.0) {\n      data2.ba = texture2D(data2_tex, coord - dx).ba;\n    } \n    else if (texture2D(data1_tex, coord + dx).a == 1.0) {\n      data2.ba = texture2D(data2_tex, coord + dx).ba;\n    } \n  }\n\n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/set-obstacle-velocity.fs.glsl',[],function () { return '// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n// texture 3: \n// - R: uWind\n// - G: vWind\n// - B: undefined\n// - A: undefined\nuniform sampler2D data3_tex;\n\nuniform vec2 grid;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  float fluidity = texture2D(data1_tex, coord).a;\n\n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y &&\n      fluidity == 0.0) {\n    \n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n\n    int count = 0;\n\n    if (texture2D(data1_tex, coord - dy).a == 1.0) {\n      count += 1;\n      vec2 data2_m_dy = texture2D(data2_tex, coord - dy).rg;\n      data2.rg = texture2D(data3_tex, coord).rg + vec2(-data2_m_dy.r, data2_m_dy.g);\n    } \n    else if (texture2D(data1_tex, coord + dy).a == 1.0) {\n      count += 1;\n      vec2 data2_p_dy = texture2D(data2_tex, coord + dy).rg;\n      data2.rg = texture2D(data3_tex, coord).rg + vec2(-data2_p_dy.r, data2_p_dy.g);\n    } \n\n    if (texture2D(data1_tex, coord - dx).a == 1.0) {\n      count += 1;\n      vec2 data2_m_dx = texture2D(data2_tex, coord - dx).rg;\n      data2.rg = texture2D(data3_tex, coord).rg + vec2(data2_m_dx.r, -data2_m_dx.g);\n    } \n    else if (texture2D(data1_tex, coord + dx).a == 1.0) {\n      count += 1;\n      vec2 data2_p_dx = texture2D(data2_tex, coord + dx).rg;\n      data2.rg = texture2D(data3_tex, coord).rg + vec2(data2_p_dx.r, -data2_p_dx.g);\n    } \n\n    if (count == 0) {\n      data2.rg = texture2D(data3_tex, coord).rg;\n    }\n  }\n  \n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/uv-to-u0v0.fs.glsl',[],function () { return '// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nvarying vec2 coord;\n\nvoid main() {\n\tvec4 data2 = texture2D(data2_tex, coord);\n\tdata2.ba = data2.rg;\n\tgl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/conserve-step1.fs.glsl',[],function () { return '// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\nuniform float i2dx;\nuniform float i2dy;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  float fluidity = texture2D(data1_tex, coord).a;\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y &&\n      fluidity == 1.0) {\n    \n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n    \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    \n    // Phi.\n    data2.b = 0.0;\n    // Div.\n    data2.a = (data2_p_dy.r - data2_m_dy.r) * i2dx + (data2_p_dx.g - data2_m_dx.g) * i2dy;\n  }\n\n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/conserve-step2.fs.glsl',[],function () { return '// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\nuniform float s;\nuniform float idxsq;\nuniform float idysq;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  float fluidity = texture2D(data1_tex, coord).a;\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y &&\n      fluidity == 1.0) {\n    \n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n    \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    \n    // Phi.\n    data2.b = s * ((data2_m_dy.b + data2_p_dy.b) * idxsq + (data2_m_dx.b + data2_p_dx.b) * idysq - data2.a);\n  }\n\n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/conserve-step3.fs.glsl',[],function () { return '// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\nuniform float i2dx;\nuniform float i2dy;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  float fluidity = texture2D(data1_tex, coord).a;\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y &&\n      fluidity == 1.0) {\n    \n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n    \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    \n    // U.\n    data2.r -= (data2_p_dy.b - data2_m_dy.b) * i2dx;\n    // V.\n    data2.g -= (data2_p_dx.b - data2_m_dx.b) * i2dy;\n  }\n\n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/diffuse.fs.glsl',[],function () { return '// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\nuniform float hx;\nuniform float hy;\nuniform float dn;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  float fluidity = texture2D(data1_tex, coord).a;\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y &&\n      fluidity == 1.0) {\n    \n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n    \n    vec4 data2_m_dy = texture2D(data2_tex, coord - dy);\n    vec4 data2_p_dy = texture2D(data2_tex, coord + dy);\n    vec4 data2_m_dx = texture2D(data2_tex, coord - dx);\n    vec4 data2_p_dx = texture2D(data2_tex, coord + dx);\n    // Update velocity UV components.\n    data2.rg = (data2.ba + hx * (data2_m_dy.rg + data2_p_dy.rg)\n                         + hy * (data2_m_dx.rg + data2_p_dx.rg)) * dn;\n  }\n\n  gl_FragColor = data2;\n}\n';});


define('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/apply-buoyancy.fs.glsl',[],function () { return '// texture 0: \n// - R: t\n// - G: t0\n// - B: tb\n// - A: conductivity\nuniform sampler2D data0_tex;\n// texture 1: \n// - R: q\n// - G: capacity\n// - B: density\n// - A: fluidity\nuniform sampler2D data1_tex;\n// texture 2: \n// - R: u\n// - G: v\n// - B: u0\n// - A: v0\nuniform sampler2D data2_tex;\n\nuniform vec2 grid;\nuniform float g;\nuniform float b;\n\nvarying vec2 coord;\n\nvoid main() {\n  vec4 data2 = texture2D(data2_tex, coord);\n  float fluidity = texture2D(data1_tex, coord).a;\n  \n  if (coord.x > grid.x && coord.x < 1.0 - grid.x &&\n      coord.y > grid.y && coord.y < 1.0 - grid.y &&\n      fluidity == 1.0) {\n    \n    vec2 dx = vec2(grid.x, 0.0);\n    vec2 dy = vec2(0.0, grid.y);\n    \n    float t = texture2D(data0_tex, coord).r;\n    // Get average column temperature.\n\n    float avg_t = t;\n    float count = 1.0;\n    vec2 n_coord = coord - dx;\n    // Silly while(true) loop (almost).\n    // While loops are not allowed.\n    // For loops with non-constant expressions also.\n    for (int i = 1; i != 0; i++) {\n      if (n_coord.x > 0.0 && texture2D(data1_tex, n_coord).a == 1.0) {\n        avg_t += texture2D(data0_tex, n_coord).r;\n        count += 1.0;\n        n_coord -= dx;\n      } else {\n        break;\n      }\n    }\n    n_coord = coord + dx;\n    // Silly while(true) loop (almost).\n    // While loops are not allowed.\n    // For loops with non-constant expressions also.\n    for (int i = 1; i != 0; i++) {\n      if (n_coord.x < 1.0 && texture2D(data1_tex, n_coord).a == 1.0) {\n        avg_t += texture2D(data0_tex, n_coord).r;\n        count += 1.0;\n        n_coord += dx;\n      } else {\n        break;\n      }\n    }\n    avg_t /= count;\n\n    // Update velocity V component.\n    data2.g += (g - b) * t + b * avg_t;\n  }\n\n  gl_FragColor = data2;\n}\n';});

/*global define: false*/

define('models/energy2d/models/physics-solvers-gpu/fluid-solver-gpu',['require','exports','module','models/energy2d/gpu/shader','models/energy2d/gpu/gpgpu','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/basic.vs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/maccormack-step1.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/maccormack-step2.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/apply-uv-boundary.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/apply-u0v0-boundary.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/set-obstacle-boundary.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/set-obstacle-velocity.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/uv-to-u0v0.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/conserve-step1.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/conserve-step2.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/conserve-step3.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/diffuse.fs.glsl','text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/apply-buoyancy.fs.glsl'],function (require, exports) {
  'use strict';
  var
    // Dependencies.
    Shader = require('models/energy2d/gpu/shader'),
    // GPGPU utilities. It's a singleton instance.
    // It should have been previously initialized by core-model.
    gpgpu  = require('models/energy2d/gpu/gpgpu'),
    // Shader sources. One of Lab build steps converts sources to JavaScript file.
    basic_vs                 = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/basic.vs.glsl'),
    maccormack_step1_fs      = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/maccormack-step1.fs.glsl'),
    maccormack_step2_fs      = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/maccormack-step2.fs.glsl'),
    apply_uv_boundary_fs     = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/apply-uv-boundary.fs.glsl'),
    apply_u0v0_boundary_fs   = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/apply-u0v0-boundary.fs.glsl'),
    set_obstacle_boundary_fs = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/set-obstacle-boundary.fs.glsl'),
    set_obstacle_velocity_fs = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/set-obstacle-velocity.fs.glsl'),
    uv_to_u0v0_fs            = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/uv-to-u0v0.fs.glsl'),
    conserve_step1_fs        = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/conserve-step1.fs.glsl'),
    conserve_step2_fs        = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/conserve-step2.fs.glsl'),
    conserve_step3_fs        = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/conserve-step3.fs.glsl'),
    diffuse_fs               = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/diffuse.fs.glsl'),
    apply_buoyancy_fs        = require('text!models/energy2d/models/physics-solvers-gpu/fluid-solver-glsl/apply-buoyancy.fs.glsl'),

    RELAXATION_STEPS = 10,
    GRAVITY = 0;

  exports.makeFluidSolverGPU = function (model) {
    var
      // ========================================================================
      // GLSL Shaders:
      // - MacCormack advection, first step.
      maccormack_step1_program      = new Shader(basic_vs, maccormack_step1_fs),
      maccormack_step2_program      = new Shader(basic_vs, maccormack_step2_fs),
      apply_uv_boundary_program     = new Shader(basic_vs, apply_uv_boundary_fs),
      apply_u0v0_boundary_program   = new Shader(basic_vs, apply_u0v0_boundary_fs),
      set_obstacle_boundary_program = new Shader(basic_vs, set_obstacle_boundary_fs),
      set_obstacle_velocity_program = new Shader(basic_vs, set_obstacle_velocity_fs),
      uv_to_u0v0_program            = new Shader(basic_vs, uv_to_u0v0_fs),
      conserve_step1_program        = new Shader(basic_vs, conserve_step1_fs),
      conserve_step2_program        = new Shader(basic_vs, conserve_step2_fs),
      conserve_step3_program        = new Shader(basic_vs, conserve_step3_fs),
      diffuse_program               = new Shader(basic_vs, diffuse_fs),
      apply_buoyancy_program        = new Shader(basic_vs, apply_buoyancy_fs),
      // ========================================================================

      // Simulation arrays provided by model.
      // texture 0:
      // - R: t
      // - G: t0
      // - B: tb
      // - A: conductivity
      data0_tex = model.getSimulationTexture(0),
      // texture 1:
      // - R: q
      // - G: capacity
      // - B: density
      // - A: fluidity
      data1_tex = model.getSimulationTexture(1),
      // texture 2:
      // - R: u
      // - G: v
      // - B: u0
      // - A: v0
      data2_tex = model.getSimulationTexture(2),
      // texture 3:
      // - R: uWind
      // - G: vWind
      // - B: undefined
      // - A: undefined
      data3_tex = model.getSimulationTexture(3),

      // Basic simulation parameters.
      props = model.getModelOptions(),
      nx = props.grid_width,
      ny = props.grid_height,

      timeStep         = props.timeStep,
      thermal_buoyancy = props.thermal_buoyancy,
      viscosity        = props.background_viscosity,

      delta_x = props.model_width / props.grid_width,
      delta_y = props.model_height / props.grid_height,

      relaxation_steps = RELAXATION_STEPS,
      gravity = GRAVITY,

      // Convenience variables.
      i2dx  = 0.5 / delta_x,
      i2dy  = 0.5 / delta_y,
      idxsq = 1.0 / (delta_x * delta_x),
      idysq = 1.0 / (delta_y * delta_y),
      s     = 0.5 / (idxsq + idysq),

      hx = timeStep * viscosity * idxsq,
      hy = timeStep * viscosity * idysq,
      dn = 1.0 / (1 + 2 * (hx + hy)),

      g = gravity * timeStep,
      b = thermal_buoyancy * timeStep,

      grid_vec = [1 / ny, 1 / nx],

      // Textures sets.
      data_2_array = [data2_tex],
      data_1_2_array = [data1_tex, data2_tex],
      data_0_1_2_array = [data0_tex, data1_tex, data2_tex],
      data_1_2_3_array = [data1_tex, data2_tex, data3_tex],

      init = function () {
        var uniforms;

        // MacCormack step 1 and 2 uniforms.
        uniforms = {
          // Texture units.
          data1_tex: 0,
          data2_tex: 1,
          // Uniforms.
          grid: grid_vec,
          tx: 0.5 * timeStep / delta_x,
          ty: 0.5 * timeStep / delta_y
        };
        maccormack_step1_program.uniforms(uniforms);
        maccormack_step2_program.uniforms(uniforms);

        // Apply UV / U0V0 boundary uniforms.
        uniforms = {
          // Texture units.
          data2_tex: 0,
          // Uniforms.
          grid: grid_vec
        };
        apply_uv_boundary_program.uniforms(uniforms);
        apply_u0v0_boundary_program.uniforms(uniforms);

        // Set obstacle boundary uniforms.
        uniforms = {
          // Texture units.
          data1_tex: 0,
          data2_tex: 1,
          // Uniforms.
          grid: grid_vec
        };
        set_obstacle_boundary_program.uniforms(uniforms);

        // Set obstacle velocity uniforms.
        uniforms = {
          // Texture units.
          data1_tex: 0,
          data2_tex: 1,
          data3_tex: 2,
          // Uniforms.
          grid: grid_vec
        };
        set_obstacle_velocity_program.uniforms(uniforms);

        // Conserve step 1 and 3 uniforms.
        uniforms = {
          // Texture units.
          data1_tex: 0,
          data2_tex: 1,
          // Uniforms.
          grid: grid_vec,
          i2dx: i2dx,
          i2dy: i2dy
        };
        conserve_step1_program.uniforms(uniforms);
        conserve_step3_program.uniforms(uniforms);

        // Conserve step 2 uniforms.
        uniforms = {
          // Texture units.
          data1_tex: 0,
          data2_tex: 1,
          // Uniforms.
          grid: grid_vec,
          s: s,
          idxsq: idxsq,
          idysq: idysq
        };
        conserve_step2_program.uniforms(uniforms);

        // Diffuse uniforms.
        uniforms = {
          // Texture units.
          data1_tex: 0,
          data2_tex: 1,
          // Uniforms.
          grid: grid_vec,
          hx: hx,
          hy: hy,
          dn: dn
        };
        diffuse_program.uniforms(uniforms);

        // Apply buoyancy uniforms.
        uniforms = {
          // Texture units.
          data0_tex: 0,
          data1_tex: 1,
          data2_tex: 2,
          // Uniforms.
          grid: grid_vec,
          g: g,
          b: b
        };
        apply_buoyancy_program.uniforms(uniforms);
      },

      applyBuoyancy = function () {
        gpgpu.executeProgram(
          apply_buoyancy_program,
          data_0_1_2_array,
          data2_tex
        );
      },

      macCormack = function () {
        // Step 1.
        gpgpu.executeProgram(
          maccormack_step1_program,
          data_1_2_array,
          data2_tex
        );
        // Apply boundary.
        gpgpu.executeProgram(
          apply_uv_boundary_program,
          data_2_array,
          data2_tex
        );
        // Step 2.
        gpgpu.executeProgram(
          maccormack_step2_program,
          data_1_2_array,
          data2_tex
        );
        // Apply boundary again.
        gpgpu.executeProgram(
          apply_uv_boundary_program,
          data_2_array,
          data2_tex
        );
      },

      conserve = function () {
        var k;
        // Step 1.
        gpgpu.executeProgram(
          conserve_step1_program,
          data_1_2_array,
          data2_tex
        );
        // Apply boundary.
        gpgpu.executeProgram(
          apply_u0v0_boundary_program,
          data_2_array,
          data2_tex
        );
        // Set obstacle boundary.
        gpgpu.executeProgram(
          set_obstacle_boundary_program,
          data_1_2_array,
          data2_tex
        );
        // Relaxation.
        for (k = 0; k < relaxation_steps; k += 1) {
          // Step 2.
          gpgpu.executeProgram(
            conserve_step2_program,
            data_1_2_array,
            data2_tex
          );
        }
        // Step 3.
        gpgpu.executeProgram(
          conserve_step3_program,
          data_1_2_array,
          data2_tex
        );
        // Apply boundary.
        gpgpu.executeProgram(
          apply_uv_boundary_program,
          data_2_array,
          data2_tex
        );
      },

      diffuse = function () {
        var k;
        // Copy UV to U0V0.
        gpgpu.executeProgram(
          uv_to_u0v0_program,
          data_2_array,
          data2_tex
        );
        // Relaxation.
        for (k = 0; k < relaxation_steps; k += 1) {
          // Step 2.
          gpgpu.executeProgram(
            diffuse_program,
            data_1_2_array,
            data2_tex
          );

          // Apply boundary.
          gpgpu.executeProgram(
            apply_uv_boundary_program,
            data_2_array,
            data2_tex
          );
        }
      },

      setObstacleVelocity = function () {
        gpgpu.executeProgram(
          set_obstacle_velocity_program,
          data_1_2_3_array,
          data2_tex
        );
      },

      copyUVtoU0V0 = function () {
        gpgpu.executeProgram(
          uv_to_u0v0_program,
          data_2_array,
          data2_tex
        );
      },

      fluid_solver_gpu = {
        solve: function () {
          if (thermal_buoyancy !== 0) {
            applyBuoyancy();
          }
          setObstacleVelocity();
          if (viscosity > 0) {
            diffuse();
            conserve();
            setObstacleVelocity();
          }
          copyUVtoU0V0();
          macCormack();
          conserve();
          setObstacleVelocity();
        }
      };

    // One-off initialization.
    init();

    return fluid_solver_gpu;
  };
});

/*jslint indent: 2, browser: true, newcap: true */
/*globals define: false*/

define('models/energy2d/models/helpers',['require','exports','module'],function (require, exports, module) {
  'use strict';

  exports.hypot = function (x, y) {
    var t;
    x = Math.abs(x);
    y = Math.abs(y);
    t = Math.min(x, y);
    x = Math.max(x, y);
    if (x === 0) return 0;
    t = t / x;
    return x * Math.sqrt(1 + t * t);
  };
});
/*jslint indent: 2, browser: true, newcap: true */
/*globals define: false*/

define('models/energy2d/models/shape',['require','exports','module'],function (require, exports, module) {
  'use strict';

  // Based on: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
  // It is optional to repeat the first vertex at the end of list of polygon vertices.
  exports.pointInsidePolygon = function (nvert, vertx, verty, testx, testy) {
    var c = 0, i, j;
    for (i = 0, j = nvert - 1; i < nvert; j = i, i += 1) {
      if (((verty[i] > testy) !== (verty[j] > testy)) &&
          (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i])) {
        c = !c;
      }
    }
    return !!c;
  };

  //
  // Line in 2D.
  //
  // It is defined by two points - (x1, y1) and (x2, y2).
  var Line = exports.Line = function (x1, y1, x2, y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
  };

  Line.prototype.intersectsLine = function (line) {
    var
      result,
      a1 = {x: this.x1, y: this.y1},
      a2 = {x: this.x2, y: this.y2},
      b1 = {x: line.x1, y: line.y1},
      b2 = {x: line.x2, y: line.y2},
      ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
      ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
      u_b  = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y),
      ua, ub;

    if (u_b !== 0) {
      ua = ua_t / u_b;
      ub = ub_t / u_b;

      if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
        result = true;
      } else {
        result = false;
      }
    } else {
      if (ua_t === 0 || ub_t === 0) {
        result = true;  // Coincident.
      } else {
        result = false; // Parallel.
      }
    }
    return result;
  };

  //
  // Polygon.
  //
  // Implements Shape2D interface:
  // - polygonize()
  // - contains(x, y)
  var Polygon = exports.Polygon = function (count, x_coords, y_coords, x, y) {
    this.count = count;
    this.raw_x_coords = x_coords;
    this.raw_y_coords = y_coords;
    this.x_coords = new Array(count);
    this.y_coords = new Array(count);
    // x_coords and y_coors will be updated now:
    this.x = x || 0;
    this.y = y || 0;
  };

  Object.defineProperty(Polygon.prototype, 'x', {
    get: function() {
      return this._x;
    },
    set: function(v) {
      var i, len;
      for (i = 0, len = this.count; i < len; i++) {
        this.x_coords[i] = this.raw_x_coords[i] + v;
      }
      this._x = v;
    }
  });

  Object.defineProperty(Polygon.prototype, 'y', {
    get: function() {
      return this._y;
    },
    set: function(v) {
      var i, len;
      for (i = 0, len = this.count; i < len; i++) {
        this.y_coords[i] = this.raw_y_coords[i] + v;
      }
      this._y = v;
    }
  });

  Polygon.prototype.polygonize = function () {
    return this;
  };

  // Based on: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
  // It is optional to repeat the first vertex at the end of list of polygon vertices.
  Polygon.prototype.contains = function (x, y) {
    var
      x_coords = this.x_coords,
      y_coords = this.y_coords,
      count = this.count,
      c = 0, i, j;

    for (i = 0, j = count - 1; i < count; j = i, i += 1) {
      if (((y_coords[i] > y) !== (y_coords[j] > y)) &&
          (x < (x_coords[j] - x_coords[i]) * (y - y_coords[i]) / (y_coords[j] - y_coords[i]) + x_coords[i])) {
        c = !c;
      }
    }
    // Convert to Boolean.
    return !!c;
  };

  //
  // Rectangle.
  // x, y - left-top corner
  //
  // Implements Shape2D interface:
  // - polygonize()
  // - contains(x, y)
  var Rectangle = exports.Rectangle = function (x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.polygon_cache = undefined;
  };

  Rectangle.prototype.polygonize = function () {
    var
      x, y, w, h;

    if (!this.polygon_cache) {
      x = this.x;
      y = this.y;
      w = this.width;
      h = this.height;
      this.polygon_cache = new Polygon(4, [x, x + w, x + w, x], [y, y, y + h, y + h]);
    }
    return this.polygon_cache;
  };

  Rectangle.prototype.contains = function (x, y) {
    return x >= this.x && x <= this.x + this.width &&
           y >= this.y && y <= this.y + this.height;
  };

  // Helper function, used by Ellipse and Ring.
  var polygonizeEllipse = function (x, y, ra, rb, segments) {
    var
      vx = new Array(segments),
      vy = new Array(segments),
      delta = 2 * Math.PI / segments,
      theta, i;

    for (i = 0; i < segments; i += 1) {
      theta = delta * i;
      vx[i] = x + ra * Math.cos(theta);
      vy[i] = y + rb * Math.sin(theta);
    }
    return new Polygon(segments, vx, vy);
  };

  //
  // Ellipse.
  // x, y - center
  // a, b - diameter (not radius)
  //
  // Implements Shape2D interface:
  // - polygonize()
  // - contains(x, y)
  var Ellipse = exports.Ellipse = function (x, y, a, b) {
    this.x = x;
    this.y = y;
    this.a = a;
    this.b = b;
    this.polygon_cache = undefined;
  };

  Ellipse.prototype.POLYGON_SEGMENTS = 50;

  Ellipse.prototype.polygonize = function () {
    if (!this.polygon_cache) {
      this.polygon_cache = polygonizeEllipse(this.x, this.y, this.a * 0.5, this.b * 0.5, this.POLYGON_SEGMENTS);
    }
    return this.polygon_cache;
  };

  Ellipse.prototype.contains = function (x, y) {
    var
      px = x - this.x,
      py = y - this.y,
      ra = this.a * 0.5,
      rb = this.b * 0.5;

    return px * px / (ra * ra) + py * py / (rb * rb) <= 1;
  };

  //
  // Ring.
  // x, y - center
  // inner, outer - diameter (not radius)
  //
  // Implements Shape2D interface:
  // - polygonize()
  // - contains(x, y)
  var Ring = exports.Ring = function (x, y, inner, outer) {
    this.x = x;
    this.y = y;
    this.inner = inner;
    this.outer = outer;
    this.polygon_cache = undefined;
  };

  Ring.prototype.POLYGON_SEGMENTS = 50;

  // Returns OUTER circle polygonization.
  Ring.prototype.polygonize = function () {
    if (!this.polygon_cache) {
      this.polygon_cache = polygonizeEllipse(this.x, this.y, this.outer * 0.5, this.outer * 0.5, this.POLYGON_SEGMENTS);
    }
    return this.polygon_cache;
  };

  Ring.prototype.contains = function (x, y) {
    var
      px = x - this.x,
      py = y - this.y,
      ra_outer = this.outer * 0.5,
      rb_outer = this.outer * 0.5,
      ra_inner = this.inner * 0.5,
      rb_inner = this.inner * 0.5;

    return (px * px / (ra_outer * ra_outer) + py * py / (rb_outer * rb_outer) <= 1) &&
           (px * px / (ra_inner * ra_inner) + py * py / (rb_inner * rb_inner) >= 1);
  };
});

/*jslint indent: 2, browser: true, newcap: true */
/*globals define: false*/

define('models/energy2d/models/photon',['require','exports','module','models/energy2d/models/helpers','models/energy2d/models/shape','models/energy2d/models/shape'],function (require, exports, module) {
  'use strict';
  var
    hypot     = require('models/energy2d/models/helpers').hypot,
    Line      = require('models/energy2d/models/shape').Line,
    Rectangle = require('models/energy2d/models/shape').Rectangle,

    //
    // Photon class.
    //
    Photon = exports.Photon = function (x, y, energy, c, angle) {
      this.x = x;
      this.y = y;
      this.energy = energy;
      this.c = c;

      if (angle !== undefined) {
        this.vx = Math.cos(angle) * c;
        this.vy = Math.sin(angle) * c;
      }
    };

  Photon.prototype.isContained = function (xmin, xmax, ymin, ymax) {
    return this.x >= xmin && this.x <= xmax && this.y >= ymin && this.y <= ymax;
  };

  Photon.prototype.move = function (dt) {
    this.x += this.vx * dt;
    this.y += this.vy * dt;
  };

  Photon.prototype.reflectFromLine = function (line, time_step) {
    var
      x1 = this.x,
      y1 = this.y,
      x2 = this.x - this.vx * time_step,
      y2 = this.y - this.vy * time_step,
      photon_line = new Line(x1, y1, x2, y2),
      vx = this.vx,
      vy = this.vy,
      r12, sin, cos, u, w;

    if (photon_line.intersectsLine(line)) {
      x1 = line.x1;
      y1 = line.y1;
      x2 = line.x2;
      y2 = line.y2;
      r12 = 1.0 / hypot(x1 - x2, y1 - y2);
      sin = (y2 - y1) * r12;
      cos = (x2 - x1) * r12;
      // Velocity component parallel to the line.
      u = vx * cos + vy * sin;
      // Velocity component perpendicular to the line.
      w = vy * cos - vx * sin;
      // Update velocities.
      this.vx = u * cos + w * sin;
      this.vy = u * sin - w * cos;
      return true;
    }
    return false;
  };

  Photon.prototype.reflectFromRectangle = function (rectangle, time_step) {
    var
      x0 = rectangle.x,
      y0 = rectangle.y,
      x1 = rectangle.x + rectangle.width,
      y1 = rectangle.y + rectangle.height,
      dx, dy;

    dx = this.vx * time_step;
    if (this.x - dx < x0) {
      this.vx = -Math.abs(this.vx);
    } else if (this.x - dx > x1) {
      this.vx = Math.abs(this.vx);
    }
    dy = this.vy * time_step;
    if (this.y - dy < y0) {
      this.vy = -Math.abs(this.vy);
    } else if (this.y - dy > y1) {
      this.vy = Math.abs(this.vy);
    }
  };

  Photon.prototype.reflectFromPolygon = function (polygon, time_step) {
    var
      line = new Line(), // no params, as this object will be reused many times
      i, len;

    for (i = 0, len = polygon.count - 1; i < len; i += 1) {
      line.x1 = polygon.x_coords[i];
      line.y1 = polygon.y_coords[i];
      line.x2 = polygon.x_coords[i + 1];
      line.y2 = polygon.y_coords[i + 1];
      if (this.reflectFromLine(line, time_step)) {
        return;
      }
    }
    line.x1 = polygon.x_coords[polygon.count - 1];
    line.y1 = polygon.y_coords[polygon.count - 1];
    line.x2 = polygon.x_coords[0];
    line.y2 = polygon.y_coords[0];
    this.reflectFromLine(line, time_step);
  };

  Photon.prototype.reflect = function (shape, time_step) {
    // Check if part contains a photon BEFORE possible polygonization.
    if (!shape.contains(this.x, this.y)) {
      return false;
    }

    if (shape instanceof Rectangle) {
      // Rectangle also can be polygonized, but for performance reasons
      // use separate method.
      this.reflectFromRectangle(shape, time_step);
    } else {
      // Other shapes (ellipses, rings, polygons) - polygonize() first
      // (polygonize() for polygon returns itself).
      this.reflectFromPolygon(shape.polygonize(), time_step);
    }
    return true;
  };
});

/*global define: false*/

define('models/energy2d/models/physics-solvers/ray-solver',['require','exports','module','models/energy2d/models/photon'],function (require, exports) {
  'use strict';
  var
    Photon = require('models/energy2d/models/photon').Photon;

  exports.makeRaySolver = function (model) {
    var
      // Basic simulation parameters.
      props = model.getModelOptions(),

      // Simulation arrays provided by model.
      q       = model.getPowerArray(),
      parts   = model.getPartsArray(),
      photons = model.getPhotonsArray(),

      // Convenience variables.
      lx = props.model_width,
      ly = props.model_height,

      nx = props.grid_width,
      ny = props.grid_height,
      nx1 = nx - 1,
      ny1 = ny - 1,

      delta_x = props.model_width / props.grid_width,
      delta_y = props.model_height / props.grid_height,

      //
      // Private methods
      //

      sunAngle = function () {
        return Math.PI - props.sun_angle;
      },

      // TODO: implement something efficient. Linked list?
      cleanupPhotonsArray = function () {
        var i = 0;
        while (i < photons.length) {
          if (photons[i] === undefined) {
            photons.splice(i, 1);
          } else {
            i += 1;
          }
        }
      },

      applyBoundary = function () {
        var i, len;
        for (i = 0, len = photons.length; i < len; i += 1) {
          if (!photons[i].isContained(0, lx, 0, ly)) {
            photons[i] = undefined;
          }
        }
        cleanupPhotonsArray();
      },

      isContained = function (x, y) {
        var i, len;
        for (i = 0, len = parts.length; i < len; i += 1) {
          if (parts[i].contains(x, y)) {
            return true;
          }
        }
        return false;
      },

      shootAtAngle = function (dx, dy) {
        var
          sun_angle = sunAngle(),
          ray_power = props.solar_power_density,
          ray_speed = props.solar_ray_speed,
          m = Math.floor(lx / dx),
          n = Math.floor(ly / dy),
          x, y, i;
        if (sun_angle >= 0 && sun_angle < 0.5 * Math.PI) {
          y = 0;
          for (i = 1; i <= m; i += 1) {
            x = dx * i;
            if (!isContained(x, y)) {
              photons.push(new Photon(x, y, ray_power, ray_speed, sun_angle));
            }
          }
          x = 0;
          for (i = 0; i <= n; i += 1) {
            y = dy * i;
            if (!isContained(x, y)) {
              photons.push(new Photon(x, y, ray_power, ray_speed, sun_angle));
            }
          }
        } else if (sun_angle < 0 && sun_angle >= -0.5 * Math.PI) {
          y = ly;
          for (i = 1; i <= m; i += 1) {
            x = dx * i;
            if (!isContained(x, y)) {
              photons.push(new Photon(x, y, ray_power, ray_speed, sun_angle));
            }
          }
          x = 0;
          for (i = 0; i <= n; i += 1) {
            y = ly - dy * i;
            if (!isContained(x, y)) {
              photons.push(new Photon(x, y, ray_power, ray_speed, sun_angle));
            }
          }
        } else if (sun_angle < Math.PI + 0.001 && sun_angle >= 0.5 * Math.PI) {
          y = 0;
          for (i = 0; i <= m; i += 1) {
            x = lx - dx * i;
            if (!isContained(x, y)) {
              photons.push(new Photon(x, y, ray_power, ray_speed, sun_angle));
            }
          }
          x = lx;
          for (i = 1; i <= n; i += 1) {
            y = dy * i;
            if (!isContained(x, y)) {
              photons.push(new Photon(x, y, ray_power, ray_speed, sun_angle));
            }
          }
        } else if (sun_angle >= -Math.PI && sun_angle < -0.5 * Math.PI) {
          y = ly;
          for (i = 0; i <= m; i += 1) {
            x = lx - dx * i;
            if (!isContained(x, y)) {
              photons.push(new Photon(x, y, ray_power, ray_speed, sun_angle));
            }
          }
          x = lx;
          for (i = 1; i <= n; i += 1) {
            y = ly - dy * i;
            if (!isContained(x, y)) {
              photons.push(new Photon(x, y, ray_power, ray_speed, sun_angle));
            }
          }
        }
      };

    return {
      solve: function () {
        var
          timeStep = props.timeStep,
          photon_emission_interval = props.photon_emission_interval,

          factor = 1.0 / (timeStep * photon_emission_interval),
          idx = 1.0 / delta_x,
          idy = 1.0 / delta_y,
          photon, part, x, y,
          i, j, photons_len, parts_len;

        for (i = 0, photons_len = photons.length; i < photons_len; i += 1) {
          photon = photons[i];
          photon.move(timeStep);

          for (j = 0, parts_len = parts.length; j < parts_len; j += 1) {
            part = parts[j];
            if (part.reflect(photon, timeStep)) {
              break;
            } else if (part.absorb(photon)) {
              x = Math.max(Math.min(Math.round(photon.x * idx), nx1), 0);
              y = Math.max(Math.min(Math.round(photon.y * idy), ny1), 0);
              q[x * ny + y] = photon.energy * factor;
              // Remove photon.
              photons[i] = undefined;
              break;
            }
          }
        }
        // Clean up absorbed photons.
        cleanupPhotonsArray();
        // Remove photons that are out of bounds.
        applyBoundary();
      },

      radiate: function () {
        var part, i, len;
        for (i = 0, len = parts.length; i < len; i += 1) {
          part = parts[i];
          if (part.emissivity > 0) {
            part.radiate(model);
          }
        }
      },

      sunShine: function () {
        var
          sun_angle = sunAngle(),
          s, c, spacing;

        if (sun_angle < 0) {
          return;
        }
        s = Math.abs(Math.sin(sun_angle));
        c = Math.abs(Math.cos(sun_angle));
        spacing = s * ly < c * lx ? ly / c : lx / s;
        spacing /= props.solar_ray_count;
        shootAtAngle(spacing / s, spacing / c);
      }
    };
  };
});

/*jslint indent: 2, browser: true, newcap: true */
/*globals define: false*/

define('models/energy2d/models/part',['require','exports','module','models/energy2d/models/constants','models/energy2d/models/helpers','models/energy2d/models/photon','models/energy2d/models/shape','models/energy2d/models/shape','models/energy2d/models/shape','models/energy2d/models/shape','models/energy2d/models/shape','models/energy2d/models/shape'],function (require, exports, module) {
  'use strict';
  var
    constants      = require('models/energy2d/models/constants'),
    hypot          = require('models/energy2d/models/helpers').hypot,
    Photon         = require('models/energy2d/models/photon').Photon,
    shape_utils    = require('models/energy2d/models/shape'),
    Line           = require('models/energy2d/models/shape').Line,
    Polygon        = require('models/energy2d/models/shape').Polygon,
    Rectangle      = require('models/energy2d/models/shape').Rectangle,
    Ellipse        = require('models/energy2d/models/shape').Ellipse,
    Ring           = require('models/energy2d/models/shape').Ring,

    // Part's constants.
    RADIATOR_SPACING = 0.5,
    MINIMUM_RADIATING_TEMPERATUE = 20,
    UNIT_SURFACE_AREA = 100,
    SIN30 = Math.sin(Math.PI / 6),
    COS30 = Math.cos(Math.PI / 6),
    SIN60 = Math.sin(Math.PI / 3),
    COS60 = Math.cos(Math.PI / 3),

    // Constructor function.
    Part = exports.Part = function (options) {
      var vertices, count, i;
      this._options = options;

      // Validate and process options.
      if (options.shapeType === "rectangle") {
        this.shape = new Rectangle(options.x, options.y, options.width, options.height);
      } else if (options.shapeType === "ellipse") {
        this.shape = new Ellipse(options.x, options.y, options.a, options.b);
      } else if (options.shapeType === "ring") {
        this.shape = new Ring(options.x, options.y, options.inner, options.outer);
      } else if (options.shapeType === "polygon") {
        vertices = options.vertices.split(', ');
        this.x_coords = [];
        this.y_coords = [];
        for (i = 0, count = vertices.length * 0.5; i < count; i += 1) {
          this.x_coords[i] = Number(vertices[2 * i]);
          this.y_coords[i] = Number(vertices[2 * i + 1]);
        }
        this.shape = new Polygon(count, this.x_coords, this.y_coords, options.x, options.y);
      } else {
        throw new Error("Part: shape not defined or not supported.");
      }
    };

  Object.defineProperty(Part.prototype, "shapeType", {
    get: function () {
      return this._options.shapeType;
    }
  });

  ["x", "y", "width", "height", "inner", "outer", "a", "b", "raw_x_coords", "raw_y_coords"].forEach(function (key) {
    Object.defineProperty(Part.prototype, key, {
      get: function () {
        return this.shape[key];
      },
      set: function (v) {
        this.shape[key] = v;
        this.polygon_cache = undefined;
      }
    });
  });

  Object.defineProperty(Part.prototype, "vertices", {
    get: function () {
      if (this.shapeType !== "polygon") return undefined;
      var x = this.shape.raw_x_coords,
          y = this.shape.raw_y_coords,
          r = [], i, len;
      for (i = 0, len = x.length; i < len; i++) {
        r.push(x[i]);
        r.push(y[i]);
      }
      return r.join(", ");
    }
  });

  ["thermal_conductivity", "specific_heat", "density", "temperature", "constant_temperature", "power", "wind_speed", "wind_angle",
   "transmission", "reflection", "absorption", "emissivity",
   "visible", "filled", "color", "texture", "label", "draggable"].forEach(function (key) {
    Object.defineProperty(Part.prototype, key, {
      get: function () {
        return this._options[key];
      },
      set: function (v) {
        this._options[key] = v;
      }
    });
  });

  Part.prototype.getLabel = function () {
    var label = this.label, s;

    function formatLabel(value, suffix) {
      var valueStr;
      if (value >= 100) {
        valueStr = value.toFixed();
      } else if (value >= 10) {
        valueStr = value.toFixed(1);
      } else {
        valueStr = value.toFixed(2);
      }
      return valueStr + suffix;
    }

    if (label === "%temperature") {
      s = formatLabel(this.temperature, " \u00b0C");
    } else if (label === "%density") {
      s = formatLabel(this.density, " kg/m\u00b3");
    } else if (label === "%specific_heat") {
      s = formatLabel(this.specific_heat, " J/(kg\u00d7\u00b0C)");
    } else if (label === "%thermal_conductivity") {
      s = formatLabel(this.thermal_conductivity, " W/(m\u00d7\u00b0C)");
    } else if (label === "%power_density") {
      s = formatLabel(this.power, " W/m\u00b3");
    } else if (label === "%area") {
      if (this.shapeType === "rectangle") {
        s = formatLabel(this.width * this.height, " m\u00b2");
      } else if (this.shapeType === "ellipse") {
        s = formatLabel(this.a * this.b * 0.25 * Math.PI, " m\u00b2");
      }
    } else if (label === "%width") {
      if (this.shapeType === "rectangle") {
        s = formatLabel(this.width, " m");
      } else if (this.shapeType === "ellipse") {
        s = formatLabel(this.a, " m");
      }
    } else if (label === "%height") {
      if (this.shapeType === "rectangle") {
        s = formatLabel(this.height, " m");
      } else if (this.shapeType === "ellipse") {
        s = formatLabel(this.b, " m");
      }
    } else {
      s = label;
    }
    return s;
  };

  // Returns cells occupied by part on the given grid
  // Grid is described by:
  //   nx - grid columns count
  //   ny - grid rows count
  //   lx - grid width
  //   ly - grid height
  // TODO: refactor it, probably using contains method.
  Part.prototype.getGridCells = function (nx, ny, lx, ly) {
    var
      nx1 = nx - 1,
      ny1 = ny - 1,
      dx = nx / lx,
      dy = ny / ly,

      rectangleIndices = function (rect) {
        var i, j, i0, j0, i_max, j_max, idx, indices = [];

        i0 = Math.min(Math.max(Math.ceil(rect.x * dx), 0), nx1);
        j0 = Math.min(Math.max(Math.ceil(rect.y * dy), 0), ny1);
        i_max = Math.min(Math.max(Math.floor((rect.x + rect.width) * dx), 0), nx1);
        j_max = Math.min(Math.max(Math.floor((rect.y + rect.height) * dy), 0), ny1);
        indices = new Array((i_max - i0 + 1) * (j_max - j0 + 1));
        idx = 0;
        for (i = i0; i <= i_max; i += 1) {
          for (j = j0; j <= j_max; j += 1) {
            indices[idx += 1] = i * ny + j;
          }
        }
        return indices;
      },

      ellipseIndices = function (ellipse) {
        var
          px = ellipse.x * dx,
          py = ellipse.y * dy,
          ra = ellipse.a * 0.5 * dx,
          rb = ellipse.b * 0.5 * dy,
          eq, i, i0, i_max, j, j0, j_max,
          idx, indices = [];

        i0 = Math.min(Math.max(Math.ceil(px - ra), 0), nx1);
        i_max = Math.min(Math.max(Math.floor(px + ra), 0), nx1);
        indices = [];
        idx = 0;
        for (i = i0; i <= i_max; i += 1) {
          // solve equation x^2/a^2 + y^2/b^2 < 1 for given x (=> i)
          // to get range of y (=> j)
          eq = Math.sqrt(1 - (i - px) * (i - px) / (ra * ra));
          j0 = Math.min(Math.max(Math.ceil(py - rb * eq), 0), ny1);
          j_max = Math.min(Math.max(Math.floor(py + rb * eq), 0), ny1);
          for (j = j0; j <= j_max; j += 1) {
            indices[idx += 1] = i * ny + j;
          }
        }
        return indices;
      },

      ringIndices = function (ring) {
        var
          px = ring.x * dx,
          py = ring.y * dy,
          ra = ring.outer * 0.5 * dx,
          rb = ring.outer * 0.5 * dy,
          ra_inner = ring.inner * 0.5 * dx,
          rb_inner = ring.inner * 0.5 * dy,
          i, i0, i_max, j, j0, j1, j2, j_max, eq,
          idx, indices = [];

        i0 = Math.min(Math.max(Math.ceil(px - ra), 0), nx1);
        i_max = Math.min(Math.max(Math.floor(px + ra), 0), nx1);

        for (i = i0; i <= i_max; i += 1) {
          // solve equation x^2/a^2 + y^2/b^2 < 1 for given x (=> i)
          // to get range of y (=> j)
          eq = Math.sqrt(1 - (i - px) * (i - px) / (ra * ra));
          j0 = Math.min(Math.max(Math.ceil(py - rb * eq), 0), ny1);
          j_max = Math.min(Math.max(Math.floor(py + rb * eq), 0), ny1);

          if (Math.abs(i - px) < ra_inner) {
            // also calculate inner ellipse
            eq = Math.sqrt(1 - (i - px) * (i - px) / (ra_inner * ra_inner));
            j1 = Math.min(Math.max(Math.ceil(py - rb_inner * eq), 0), ny1);
            j2 = Math.min(Math.max(Math.floor(py + rb_inner * eq), 0), ny1);
            for (j = j0; j <= j1; j += 1) {
              indices[idx += 1] = i * ny + j;
            }
            for (j = j2; j <= j_max; j += 1) {
              indices[idx += 1] = i * ny + j;
            }
          } else {
            // consider only outer ellipse
            for (j = j0; j <= j_max; j += 1) {
              indices[idx += 1] = i * ny + j;
            }
          }
        }
        return indices;
      },

      polygonIndices = function (polygon) {
        var
          count = polygon.x_coords.length,
          x_coords = new Array(count),
          y_coords = new Array(count),
          x_min = Number.MAX_VALUE, x_max = Number.MIN_VALUE,
          y_min = Number.MAX_VALUE, y_max = Number.MIN_VALUE,
          i, i0, i_max, j, j0, j_max,
          idx, indices = [];

        for (i = 0; i < count; i += 1) {
          x_coords[i] = polygon.x_coords[i] * dx;
          y_coords[i] = polygon.y_coords[i] * dy;
          if (x_coords[i] < x_min) {
            x_min = x_coords[i];
          }
          if (x_coords[i] > x_max) {
            x_max = x_coords[i];
          }
          if (y_coords[i] < y_min) {
            y_min = y_coords[i];
          }
          if (y_coords[i] > y_max) {
            y_max = y_coords[i];
          }
        }

        i0 = Math.min(Math.max(Math.round(x_min), 0), nx1);
        j0 = Math.min(Math.max(Math.round(y_min), 0), ny1);
        i_max = Math.min(Math.max(Math.round(x_max), 0), nx1);
        j_max = Math.min(Math.max(Math.round(y_max), 0), ny1);
        indices = [];
        idx = 0;
        for (i = i0; i <= i_max; i += 1) {
          for (j = j0; j <= j_max; j += 1) {
            if (shape_utils.pointInsidePolygon(count, x_coords, y_coords, i, j)) {
              indices[idx += 1] = i * ny + j;
            }
          }
        }
        return indices;
      };

    if (this.shapeType === "rectangle") {
      return rectangleIndices(this.shape);
    }
    if (this.shapeType === "ellipse") {
      return ellipseIndices(this.shape);
    }
    if (this.shapeType === "ring") {
      return ringIndices(this.shape);
    }
    if (this.shapeType === "polygon") {
      return polygonIndices(this.shape);
    }
    throw new Error("Part: unknown shape.");
  };

  // Tests if the specified coordinates are inside the boundary of the Part.
  Part.prototype.contains = function (x, y) {
    return this.shape.contains(x, y);
  };

  // Test whether part reflects given Photon p.
  Part.prototype.reflect = function (p, time_step) {
    // Try to reflect when part's reflection equals ~1.
    if (Math.abs(this.reflection - 1) < 0.001) {
      return p.reflect(this.shape, time_step);
    }
    // Other case.
    return false;
  };

  // Test whether part absorbs given Photon p.
  Part.prototype.absorb = function (p) {
    // Absorb when absorption equals ~1 and photon is inside part's shape.
    if (Math.abs(this.absorption - 1) < 0.001) {
      return this.shape.contains(p.x, p.y);
    }
    // Other case.
    return false;
  };

  Part.prototype.getIrradiance = function (temperature) {
    var t2;
    if (this.emissivity === 0) {
      return 0;
    }
    t2 = 273 + temperature;
    t2 *= t2;
    return this.emissivity * constants.STEFAN_CONSTANT * UNIT_SURFACE_AREA * t2 * t2;
  };

  // Emit photons if part meets radiation conditions.
  Part.prototype.radiate = function (model) {
    var
      // The shape is polygonized and radiateFromLine() is called for each line.
      poly = this.shape.polygonize(),
      line = new Line(),
      i, len;

    if (this.emissivity === 0) {
      return;
    }
    // Must follow the clockwise direction in setting lines.
    for (i = 0, len = poly.count - 1; i < len; i += 1) {
      line.x1 = poly.x_coords[i];
      line.y1 = poly.y_coords[i];
      line.x2 = poly.x_coords[i + 1];
      line.y2 = poly.y_coords[i + 1];
      this.radiateFromLine(model, line);
    }
    line.x1 = poly.x_coords[poly.count - 1];
    line.y1 = poly.y_coords[poly.count - 1];
    line.x2 = poly.x_coords[0];
    line.y2 = poly.y_coords[0];
    this.radiateFromLine(model, line);
  };

  // Helper function for radiate() method.
  Part.prototype.radiateFromLine = function (model, line) {
    var options, length, cos, sin, n, x, y, p, d, vx, vy, vxi, vyi, nray, ir,
      i, k;

    if (this.emissivity === 0) {
      return;
    }
    options = model.getModelOptions();
    length = hypot(line.x1 - line.x2, line.y1 - line.y2);
    cos = (line.x2 - line.x1) / length;
    sin = (line.y2 - line.y1) / length;
    n = Math.max(1, Math.round(length / RADIATOR_SPACING));
    vx = options.solar_ray_speed * sin;
    vy = -options.solar_ray_speed * cos;
    if (n === 1) {
      d = 0.5 * length;
      x = line.x1 + d * cos;
      y = line.y1 + d * sin;
      d = model.getAverageTemperatureAt(x, y);
      if (d > MINIMUM_RADIATING_TEMPERATUE) {
        d = model.getTemperatureAt(x, y);
        p = new Photon(x, y, this.getIrradiance(d), options.solar_ray_speed);
        p.vx = vx;
        p.vy = vy;
        model.addPhoton(p);
        if (!this.constant_temperature) {
          model.setTemperatureAt(x, y, d - p.energy / this.specific_heat);
        }
      }
    } else {
      vxi = new Array(4);
      vyi = new Array(4);
      vxi[0] = vx * COS30 - vy * SIN30;
      vyi[0] = vx * SIN30 + vy * COS30;
      vxi[1] = vy * SIN30 + vx * COS30;
      vyi[1] = vy * COS30 - vx * SIN30;
      vxi[2] = vx * COS60 - vy * SIN60;
      vyi[2] = vx * SIN60 + vy * COS60;
      vxi[3] = vy * SIN60 + vx * COS60;
      vyi[3] = vy * COS60 - vx * SIN60;
      nray = 1 + vxi.length;
      for (i = 0; i < n; i += 1) {
        d = (i + 0.5) * RADIATOR_SPACING;
        x = line.x1 + d * cos;
        y = line.y1 + d * sin;
        d = model.getAverageTemperatureAt(x, y);
        ir = this.getIrradiance(d) / nray;
        if (d > MINIMUM_RADIATING_TEMPERATUE) {
          p = new Photon(x, y, ir, options.solar_ray_speed);
          p.vx = vx;
          p.vy = vy;
          model.addPhoton(p);
          for (k = 0; k < nray - 1; k += 1) {
            p = new Photon(x, y, ir, options.solar_ray_speed);
            p.vx = vxi[k];
            p.vy = vyi[k];
            model.addPhoton(p);
          }
          if (!this.constant_temperature) {
            model.changeAverageTemperatureAt(x, y, -ir * nray / this.specific_heat);
          }
        }
      }
    }
  };
});

/*global define: false */

define('models/energy2d/models/core-model',['require','exports','module','arrays','models/energy2d/models/physics-solvers/heat-solver','models/energy2d/models/physics-solvers-gpu/heat-solver-gpu','models/energy2d/models/physics-solvers/fluid-solver','models/energy2d/models/physics-solvers-gpu/fluid-solver-gpu','models/energy2d/models/physics-solvers/ray-solver','models/energy2d/models/part','models/energy2d/gpu/gpgpu','models/energy2d/models/helpers'],function (require, exports) {
  'use strict';

  var
    arrays          = require('arrays'),
    heatsolver      = require('models/energy2d/models/physics-solvers/heat-solver'),
    heatsolver_GPU  = require('models/energy2d/models/physics-solvers-gpu/heat-solver-gpu'),
    fluidsolver     = require('models/energy2d/models/physics-solvers/fluid-solver'),
    fluidsolver_GPU = require('models/energy2d/models/physics-solvers-gpu/fluid-solver-gpu'),
    raysolver       = require('models/energy2d/models/physics-solvers/ray-solver'),
    Part            = require('models/energy2d/models/part').Part,
    gpgpu           = require('models/energy2d/gpu/gpgpu'),
    hypot           = require('models/energy2d/models/helpers').hypot,

    array_type = (function () {
      try {
        new Float32Array();
      } catch (e) {
        return 'regular';
      }
      return 'Float32Array';
    }());

  // Core Energy2D model.
  //
  // It creates and manages all the data and parameters used for calculations.
  exports.makeCoreModel = function (opt, partsOpt) {
    var
      // Simulation grid dimensions.
      nx = opt.grid_width,
      ny = opt.grid_height,
      array_size = nx * ny,

      // Spacing.
      delta_x = opt.model_width / nx,
      delta_y = opt.model_height / ny,

      // Simulation steps counter.
      indexOfStep = 0,

      // Physics solvers
      // (initialized later, when core model object is built).
      heatSolver,
      fluidSolver,
      ray_solver,

      // GPU versions of solvers.
      heat_solver_gpu,
      fluid_solver_gpu,

      // Optimization flags.
      radiative,
      has_part_power,

      // WebGL GPGPU optimization.
      WebGL_active = false,
      // This variable holds possible error message connected with WebGL.
      WebGL_error,

      // Performance model.
      // By default, mock this object.
      // To measure performance, set valid object
      // using core_model.setPerformanceTools(tools);
      perf = {
        start: function () {},
        stop: function () {},
        startFPS: function () {},
        updateFPS: function () {},
        stopFPS: function () {}
      },

      //
      // Simulation arrays:
      //
      // - temperature array
      t = arrays.create(array_size, opt.background_temperature, array_type),
      // - internal temperature boundary array
      tb = arrays.create(array_size, NaN, array_type),
      // - velocity x-component array (m/s)
      u = arrays.create(array_size, 0, array_type),
      // - velocity y-component array (m/s)
      v = arrays.create(array_size, 0, array_type),
      // - internal heat generation array
      q = arrays.create(array_size, 0, array_type),
      // - wind speed
      uWind = arrays.create(array_size, 0, array_type),
      vWind = arrays.create(array_size, 0, array_type),
      // - conductivity array
      conductivity = arrays.create(array_size, opt.background_conductivity, array_type),
      // - specific heat capacity array
      capacity = arrays.create(array_size, opt.background_specific_heat, array_type),
      // - density array
      density = arrays.create(array_size, opt.background_density, array_type),
      // - fluid cell array
      fluidity = arrays.create(array_size, true, array_type),
      // - photons array
      photons = [],

      //
      // [GPGPU] Simulation textures:
      //
      // texture 0:
      // - R: t
      // - G: t0
      // - B: tb
      // - A: conductivity
      // texture 1:
      // - R: q
      // - G: capacity
      // - B: density
      // - A: fluidity
      // texture 2:
      // - R: u
      // - G: v
      // - B: u0
      // - A: v0
      // texture 3:
      // - R: uWind
      // - G: vWind
      // - B: undefined
      // - A: undefined
      texture = [],

      // Generate parts array.
      parts = (function () {
        var result = [],
            i, len;

        if (partsOpt) {
          if (!arrays.isArray(partsOpt)) {
            partsOpt = [partsOpt];
          }
          result = new Array(partsOpt.length);
          for (i = 0, len = partsOpt.length; i < len; i += 1) {
            result[i] = new Part(partsOpt[i]);
          }
        }
        return result;
      }()),

      //
      // Private methods
      //
      initGPGPU = function () {
        WebGL_active = false;

        // Make sure that environment is a browser.
        if (typeof window === 'undefined') {
          throw new Error("Core model: WebGL GPGPU unavailable in the node.js environment.");
        }
        // Init module.
        // Width is ny, height is nx (due to data organization).
        gpgpu.init(ny, nx);

        // Create simulation textures.
        texture[0] = gpgpu.createTexture();
        texture[1] = gpgpu.createTexture();
        texture[2] = gpgpu.createTexture();
        texture[3] = gpgpu.createTexture();

        // Update textures as material properties should be already set.
        fillGPGPUTextures();

        // Create GPU solvers.
        // GPU version of heat solver.
        heat_solver_gpu = heatsolver_GPU.makeHeatSolverGPU(core_model);
        // GPU version of fluid solver.
        fluid_solver_gpu = fluidsolver_GPU.makeFluidSolverGPU(core_model);

        WebGL_active = true;
      },

      fillGPGPUTextures = function () {
        // texture 0:
        // - R: t
        // - G: t0
        // - B: tb
        // - A: conductivity
        gpgpu.writeRGBATexture(texture[0], t, t, tb, conductivity);
        // texture 1:
        // - R: q
        // - G: capacity
        // - B: density
        // - A: fluidity
        gpgpu.writeRGBATexture(texture[1], q, capacity, density, fluidity);
        // texture 2:
        // - R: u
        // - G: v
        // - B: u0
        // - A: v0
        gpgpu.writeRGBATexture(texture[2], u, v, u, v);
        // texture 3:
        // - R: uWind
        // - G: vWind
        // - B: undefined
        // - A: undefined
        gpgpu.writeRGBATexture(texture[3], uWind, vWind, uWind, vWind);
      },

      setupOptimizationFlags = function () {
        radiative = (function () {
          var i, len;
          if (opt.sunny) {
            return true;
          }
          for (i = 0, len = parts.length; i < len; i += 1) {
            if (parts[i].emissivity > 0) {
              return true;
            }
          }
          return false;
        }());

        has_part_power = (function () {
          var i, len;
          for (i = 0, len = parts.length; i < len; i += 1) {
            if (parts[i].power > 0) {
              return true;
            }
          }
          return false;
        }());
      },

      setupPart = function (part, updateOnly) {
        var
          lx = opt.model_width,
          ly = opt.model_height,
          indices, idx,
          ii, len;

        indices = part.getGridCells(nx, ny, lx, ly);
        for (ii = 0, len = indices.length; ii < len; ii += 1) {
          idx = indices[ii];

          if (!updateOnly) {
            t[idx] = part.temperature;
          }
          fluidity[idx] = false;
          conductivity[idx] = part.thermal_conductivity;
          capacity[idx] = part.specific_heat;
          density[idx] = part.density;

          if (part.wind_speed !== 0) {
            uWind[idx] = part.wind_speed * Math.cos(part.wind_angle);
            vWind[idx] = part.wind_speed * Math.sin(part.wind_angle);
          }

          if (part.constant_temperature) {
            tb[idx] = part.temperature;
          }
        }
      },

      resetArrays = function (skipTempAndVelocity) {
        if (!skipTempAndVelocity) {
          arrays.fill(t, opt.background_temperature);
          arrays.fill(u, 0);
          arrays.fill(v, 0);
        }
        arrays.fill(tb, NaN);
        arrays.fill(q, 0);
        arrays.fill(uWind, 0);
        arrays.fill(vWind, 0);
        arrays.fill(conductivity, opt.background_conductivity);
        arrays.fill(capacity, opt.background_specific_heat);
        arrays.fill(density, opt.background_density);
        arrays.fill(fluidity, true);
      },

      setupMaterialProperties = function (updateOnly) {
        if (!parts || parts.length === 0) return;
        var i;
        // Treat overlapping parts as original Energy2D.
        for (i = parts.length - 1; i >= 0; i -= 1) {
          setupPart(parts[i], updateOnly);
        }
      },

      refreshPowerArray = function () {
        var part, x, y, i, iny, j, k, len, count;
        for (i = 0; i < nx; i += 1) {
          x = i * delta_x;
          iny = i * ny;
          for (j = 0; j < ny; j += 1) {
            y = j * delta_y;
            q[iny + j] = 0;
            count = 0;
            if (has_part_power) {
              for (k = 0, len = parts.length; k < len; k += 1) {
                part = parts[k];
                if (part.shape.contains(x, y)) {
                  q[iny + j] += part.power;
                  count++;
                }
              }
              if (count > 0) q[iny + j] /= count;
            }
          }
        }
      },

      getVorticityAt = function (i, j) {
        var du_dy = (u[i * ny + j + 1] - u[i * ny + j - 1]) / delta_x,
            dv_dx = (v[(i + 1) * ny + j] - v[(i - 1) * ny + j]) / delta_y;
        return 0.5 * (du_dy - dv_dx);
      },

      //
      // Public API
      //
      core_model = {
        // !!!
        // Performs next step of a simulation.
        // !!!
        nextStep: function () {
          perf.start('Core model step');
          if (WebGL_active) {
            // GPU solvers.
            if (opt.convective) {
              perf.start('Fluid solver GPU');
              fluid_solver_gpu.solve();
              perf.stop('Fluid solver GPU');
            }
            perf.start('Heat solver GPU');
            heat_solver_gpu.solve(opt.convective);
            perf.stop('Heat solver GPU');
          } else {
            // CPU solvers.
            if (radiative) {
              perf.start('Ray solver CPU');
              if (indexOfStep % opt.photon_emission_interval === 0) {
                refreshPowerArray();
                if (opt.sunny) {
                  ray_solver.sunShine();
                }
                ray_solver.radiate();
              }
              ray_solver.solve();
              perf.stop('Ray solver CPU');
            }
            if (opt.convective) {
              perf.start('Fluid solver CPU');
              fluidSolver.solve(u, v);
              perf.stop('Fluid solver CPU');
            }
            perf.start('Heat solver CPU');
            heatSolver.solve(opt.convective, t, q);
            perf.stop('Heat solver CPU');
          }
          indexOfStep += 1;
          perf.stop('Core model step');
        },

        // Sets performance tools.
        // It's expected to be an object created by
        // energy2d.utils.performance.makePerformanceTools
        setPerformanceTools: function (perf_tools) {
          perf = perf_tools;
        },

        reset: function () {
          indexOfStep = 0;
          resetArrays();
          setupOptimizationFlags();
          setupMaterialProperties();
          refreshPowerArray();
          if (WebGL_active) {
            fillGPGPUTextures();
          }
        },

        partsChanged: function (part, propChanged) {
          // TODO: in theory we don't have to process all parts. If needed
          // implement something tricker.
          // Note that temperature and velocity aren't reset to provide better
          // interactivity.
          resetArrays(true);
          setupOptimizationFlags();
          setupMaterialProperties(true);
          refreshPowerArray();

          if (propChanged === "temperature") {
            setupPart(part);
          }
        },

        addPart: function (props) {
          var part = new Part(props);
          parts.push(part);
          setupPart(part);
        },

        removePart: function (i) {
          parts.splice(i, 1);
          core_model.partsChanged();
        },

        useWebGL: function (v) {
          if (WebGL_active === v) return;
          if (!core_model.isWebGLCompatible()) {
            // Some models are incompatible with WebGL.
            WebGL_active = false;
            return;
          }

          if (v) {
            // Initialize GPGPU, this will also copy current temperature
            // and velocity arrays into textures.
            initGPGPU();
          } else {
            // Copy data back from GPU to main memory.
            core_model.syncTemperature();
            core_model.syncVelocity();
            WebGL_active = false;
          }
        },

        isWebGLActive: function () {
          return WebGL_active;
        },

        isWebGLCompatible: function () {
          return !radiative;
        },

        getWebGLError: function () {
          return WebGL_error;
        },

        syncTemperature: function () {
          if (WebGL_active) {
            gpgpu.readTexture(texture[0], t);
          }
        },

        syncVelocity: function () {
          if (WebGL_active) {
            gpgpu.readTexture(texture[2], u, 0);
            gpgpu.readTexture(texture[2], v, 1);
          }
        },

        getIndexOfStep: function () {
          return indexOfStep;
        },
        // Returns loaded options after validation.
        getModelOptions: function () {
          return opt;
        },

        // Temperature manipulation.
        getTemperatureAt: function (x, y) {
          var
            i = Math.max(Math.min(nx - 1, Math.round(x / delta_x)), 0),
            j = Math.max(Math.min(ny - 1, Math.round(y / delta_y)), 0);

          return t[i * ny + j];
        },

        setTemperatureAt: function (x, y, temperature) {
          var
            i = Math.max(Math.min(nx - 1, Math.round(x / delta_x)), 0),
            j = Math.max(Math.min(ny - 1, Math.round(y / delta_y)), 0);

          t[i * ny + j] = temperature;
        },

        getAverageTemperatureAt: function (x, y) {
          var
            temp = 0,
            nx1 = nx - 1,
            ny1 = ny - 1,
            i0 = Math.round(x / delta_x),
            j0 = Math.round(y / delta_y),
            i, j;

          i = Math.max(Math.min(nx1, i0), 0);
          j = Math.max(Math.min(ny1, j0), 0);
          temp += t[i * ny + j];
          i = Math.max(Math.min(nx1, i0 + 1), 0);
          j = Math.max(Math.min(ny1, j0), 0);
          temp += t[i * ny + j];
          i = Math.max(Math.min(nx1, i0 - 1), 0);
          j = Math.max(Math.min(ny1, j0), 0);
          temp += t[i * ny + j];
          i = Math.max(Math.min(nx1, i0), 0);
          j = Math.max(Math.min(ny1, j0 + 1), 0);
          temp += t[i * ny + j];
          i = Math.max(Math.min(nx1, i0), 0);
          j = Math.max(Math.min(ny1, j0 - 1), 0);
          temp += t[i * ny + j];
          return temp * 0.2;
        },

        // TODO: based on Java version, check it as the logic seems to be weird.
        changeAverageTemperatureAt: function (x, y, increment) {
          var
            nx1 = nx - 1,
            ny1 = ny - 1,
            i0 = Math.round(x / delta_x),
            j0 = Math.round(y / delta_y),
            i, j;

          increment *= 0.2;
          i = Math.min(nx1, i0);
          j = Math.min(ny1, j0);
          if (i >= 0 && j >= 0) {
            t[i * ny + j] += increment;
          }
          i = Math.min(nx1, i0 + 1);
          j = Math.min(ny1, j0);
          if (i >= 0 && j >= 0) {
            t[i * ny + j] += increment;
          }
          i = Math.min(nx1, i0 - 1);
          j = Math.min(ny1, j0);
          if (i >= 0 && j >= 0) {
            t[i * ny + j] += increment;
          }
          i = Math.min(nx1, i0);
          j = Math.min(ny1, j0 + 1);
          if (i >= 0 && j >= 0) {
            t[i * ny + j] += increment;
          }
          i = Math.min(nx1, i0);
          j = Math.min(ny1, j0 - 1);
          if (i >= 0 && j >= 0) {
            t[i * ny + j] += increment;
          }
        },

        getVorticityAt: function (x, y) {
          var i = Math.max(Math.min(nx - 1, Math.round(x / delta_x)), 0),
              j = Math.max(Math.min(ny - 1, Math.round(y / delta_y)), 0);
          return getVorticityAt(i, j);
        },

        getAverageVorticityAt: function (x, y) {
          var i = Math.max(Math.min(nx - 1, Math.round(x / delta_x)), 0),
              j = Math.max(Math.min(ny - 1, Math.round(y / delta_y)), 0),
              vor = getVorticityAt(i, j);
          vor += getVorticityAt(i - 1, j);
          vor += getVorticityAt(i + 1, j);
          vor += getVorticityAt(i, j - 1);
          vor += getVorticityAt(i, j + 1);
          vor += getVorticityAt(i - 1, j - 1);
          vor += getVorticityAt(i - 1, j + 1);
          vor += getVorticityAt(i + 1, j - 1);
          vor += getVorticityAt(i + 1, j + 1);
          return vor / 9;
        },

        getSpeedAt: function (x, y) {
          var
            i = Math.max(Math.min(nx - 1, Math.round(x / delta_x)), 0),
            j = Math.max(Math.min(ny - 1, Math.round(y / delta_y)), 0);

          return hypot(u[i * ny + j], v[i * ny + j]);
        },

        getHeatFluxAt: function (x, y) {
          var
            i = Math.max(Math.min(nx - 1, Math.round(x / delta_x)), 0),
            j = Math.max(Math.min(ny - 1, Math.round(y / delta_y)), 0),
            fx = conductivity[i * ny + j] * (t[(i - 1) * ny + j] - t[(i + 1) * ny + j]) / (2 * delta_x),
            fy = conductivity[i * ny + j] * (t[i * ny + j - 1] - t[i * ny + j + 1]) / (2 * delta_y);
          return [fx, fy];
        },

        addPhoton: function (photon) {
          photons.push(photon);
        },

        removePhoton: function (photon) {
          var idx = photons.indexOf(photon);
          if (idx !== -1) {
            photons.splice(idx, 1);
          }
        },

        // Simple getters.
        getArrayType: function () {
          // return module variable
          return array_type;
        },
        getPerformanceModel: function () {
          return perf;
        },
        // Arrays.
        getTemperatureArray: function () {
          return t;
        },
        getUVelocityArray: function () {
          return u;
        },
        getVVelocityArray: function () {
          return v;
        },
        getUWindArray: function () {
          return uWind;
        },
        getVWindArray: function () {
          return vWind;
        },
        getBoundaryTemperatureArray: function () {
          return tb;
        },
        getPowerArray: function () {
          return q;
        },
        getConductivityArray: function () {
          return conductivity;
        },
        getCapacityArray: function () {
          return capacity;
        },
        getDensityArray: function () {
          return density;
        },
        getFluidityArray: function () {
          return fluidity;
        },
        getPhotonsArray: function () {
          return photons;
        },
        getPartsArray: function () {
          return parts;
        },
         // Textures.
        getTemperatureTexture: function () {
          return texture[0];
        },
        getVelocityTexture: function () {
          return texture[2];
        },
        getSimulationTexture: function (id) {
          return texture[id];
        }
      };

    //
    // One-off initialization.
    //
    setupOptimizationFlags();
    setupMaterialProperties();
    refreshPowerArray();

    // CPU version of solvers.
    heatSolver = heatsolver.makeHeatSolver(core_model);
    fluidSolver = fluidsolver.makeFluidSolver(core_model);
    ray_solver = raysolver.makeRaySolver(core_model);

    // Finally, return public API object.
    return core_model;
  };
});
/*global define: false, d3: false */

define('models/energy2d/modeler',['require','common/alert','common/console','common/validator','common/serialize','common/performance','common/lab-modeler-mixin','models/energy2d/metadata','models/energy2d/models/core-model'],function (require) {
  'use strict';
  var alert           = require('common/alert'),
      console         = require('common/console'),
      validator       = require('common/validator'),
      serialize       = require('common/serialize'),
      performance     = require('common/performance'),
      LabModelerMixin = require('common/lab-modeler-mixin'),
      metadata        = require('models/energy2d/metadata'),
      coremodel       = require('models/energy2d/models/core-model'),

      unitsDefinition = {
        units: {
          time: {
            name: "second",
            pluralName: "seconds",
            symbol: "s"
          },
          temperature: {
            name: "degree Celsius",
            pluralName: "degrees Celsius",
            symbol: "°C"
          },
          length: {
            name: "meter",
            pluralName: "meters",
            symbol: "m"
          },
          velocity: {
            name: "meter per second",
            pluralName: "meters per second",
            symbol: "m/s"
          }
        }
      },

      energy2dModelCount = 0;


  return function Modeler(initialProperties) {
    var model,
        coreModel,
        namespace = "energy2dModel" + (++energy2dModelCount),

        labModelerMixin = new LabModelerMixin({
          metadata: metadata,
          unitsDefinition: unitsDefinition,
          initialProperties: initialProperties,
          setters: {
            use_WebGL: function (v) {
              if (coreModel) {
                setWebGLEnabled(v);
              }
              ticksToGPUSync = model.properties.ticksPerGPUSync;
            },
            ticksPerGPUSync: function (v) {
              if (coreModel) syncGPU();
              ticksToGPUSync = Number(v); // support "Infinity" value
            }
          }
        }),
        propertySupport = labModelerMixin.propertySupport,
        dispatch = labModelerMixin.dispatchSupport,

        ticksToGPUSync = 0,

        // Sensors are modeler-level objects, they only define outputs
        // and have nothing to do with physics calculations.
        sensors = [],
        anemometers = [],

        viewModel = {
          parts: [],
          sensors: []
        },

        updatePartsViewModel = (function () {
          function PartWrapper(rawPart) {
            Object.defineProperty(this, '_rawPart', {
              enumerable: false,
              get: function () {
                return rawPart;
              }
            });
          }
          Object.keys(metadata.part).forEach(function (key) {
            Object.defineProperty(PartWrapper.prototype, key, {
              enumerable: true,
              get: function () {
                return this._rawPart[key];
              },
              set: function (v) {
                var WebGLOrg = model.properties.use_WebGL;
                // This will update CPU array.
                model.properties.use_WebGL = false;

                propertySupport.invalidatingChangePreHook();

                // Update raw part object.
                this._rawPart[key] = validator.validateSingleProperty(metadata.part[key], key, v);

                if (model.isStopped()) {
                  // Recalculate all arrays, "authoring" mode.
                  coreModel.reset();
                } else {
                  // Update core model arrays based on part's properties.
                  coreModel.partsChanged(this._rawPart, key);
                }

                propertySupport.invalidatingChangePostHook();

                // Restore original WebGL option value. It will
                // copy CPU arrays to GPU in case of need.
                model.properties.use_WebGL = WebGLOrg;
                dispatch.partsChanged();
              }
            });
          });
          PartWrapper.prototype.computeLabel = function() {
            return this._rawPart.getLabel();
          };

          return function () {
            var rawParts = coreModel.getPartsArray(),
                viewParts = viewModel.parts,
                i, len;
            viewParts.length = 0;
            for (i = 0, len = rawParts.length; i < len; i++) {
              viewParts.push(new PartWrapper(rawParts[i]));
            }
          };
        }()),

        updateSensorViewModel = (function () {
          function SensorWrapper(rawObj) {
            Object.defineProperty(this, '_rawObj', {
              enumerable: false,
              get: function () {
                return rawObj;
              }
            });
          }
          var constraint = {
            x: function (v) { return Math.max(0, Math.min(model.properties.model_width, v)); },
            y: function (v) { return Math.max(0, Math.min(model.properties.model_height, v)); }
          };
          Object.keys(metadata.sensor).forEach(function (key) {
            Object.defineProperty(SensorWrapper.prototype, key, {
              enumerable: true,
              get: function () {
                return this._rawObj[key];
              },
              set: function (v) {
                propertySupport.invalidatingChangePreHook();
                v = constraint[key] ? constraint[key](v) : v;
                this._rawObj[key] = validator.validateSingleProperty(metadata.sensor[key], key, v);
                propertySupport.invalidatingChangePostHook();
                dispatch.sensorsChanged();
              }
            });
          });

          return function () {
            var viewSensors = viewModel.sensors,
                i, len;
            viewSensors.length = 0;
            for (i = 0, len = sensors.length; i < len; i++) {
              viewSensors.push(new SensorWrapper(sensors[i]));
            }
          };
        }());

    function setWebGLEnabled(v) {
      try {
        coreModel.useWebGL(v);
      } catch (e) {
        console.warn("WebGL initialization failed. CPU solvers and rendering will be used.");
        console.warn(e.message);
      }
    }

    function syncGPU() {
      coreModel.syncTemperature();
      coreModel.syncVelocity();
    }

    function hasDiverged() {
      var t = model.getTemperatureArray(),
          i, len;

      for (i = 0, len = t.length; i < len; i++) {
        if (isNaN(t[i]) || Math.abs(t[i]) > 1e10) {
          model.stop();
          coreModel.reset();
          return true;
        }
      }
      return false;
    }

    function validateParts(partsArray) {
      var result = [];
      partsArray.forEach(function (v) {
        result.push(validator.validateCompleteness(metadata.part, v));
      });
      return result;
    }

    function createSensors(sensorsSpec) {
      var sensorValue = {
            thermometer: function () {
              return model.getTemperatureAt(this.x, this.y);
            },
            anemometer: function () {
              return this._rot;
            },
            heatFlux: function () {
              var flux = model.getHeatFluxAt(this.x, this.y);
              return flux[0] * this._sin + flux[1] * this._cos;
            }
          },
          sensorOutputDesc = {
            thermometer: {
              label: "Temperature",
              unitType: 'temperature',
              format: '.1f'
            },
            anemometer: {
            },
            heatFlux: {
              label: "Heat Flux",
              unitName: "Watt Per Square Meter",
              unitPluralName: "Watts Per Square Meter",
              unitAbbreviation: "W/m²",
              format: '.1f'
            }
          };

      sensors = [];
      sensorsSpec.forEach(function (s, idx) {
        s = validator.validateCompleteness(metadata.sensor, s);
        if (s.type === "anemometer") {
          s._rot = 0;
          anemometers.push(s);
        }
        if (s.type === "heatFlux") {
          s.angle = s.angle || 0;
          s._sin = Math.sin(-s.angle * Math.PI / 180);
          s._cos = Math.cos(s.angle * Math.PI / 180);
        }
        Object.defineProperty(s, "value", {
          enumerable: true,
          get: sensorValue[s.type]
        });
        sensors.push(s);

        model.defineOutput("sensor-" + idx, sensorOutputDesc[s.type], function() {
          return s.value;
        });
      });
    }

    function updateAnemometers() {
      var a, i, len;
      for (i = 0, len = anemometers.length; i < len; i++) {
        a = anemometers[i];
        a._rot += model.getSpeedAt(a.x, a.y) * (model.getVorticityAt(a.x, a.y) < 0 ? -1 : 1) *
                  model.properties.timeStep * 700;
        a._rot = a._rot % 360;
      }
    }

    model = {
      namespace: namespace,

      tick: function () {
        var i, len, diverged;

        performance.enterScope("engine");
        for (i = 0, len = model.properties.timeStepsPerTick; i < len; i++) {
          coreModel.nextStep();
        }
        performance.leaveScope("engine");

        if (coreModel.isWebGLActive()) {
          if (ticksToGPUSync > 0) {
            ticksToGPUSync--;
          } else {
            syncGPU();
            ticksToGPUSync = Number(model.properties.ticksPerGPUSync); // support "Infinity" value
            diverged = hasDiverged();
          }
        } else {
          diverged = hasDiverged();
        }
        updateAnemometers();
        model.updateAllOutputProperties();
        dispatch.tick();

        if (diverged) {
          alert("The model has diverged and has been reset!\n\nTry changing its parameters " +
                "(e.g. 'timeStep', positions of parts etc.) or reload it to restore the initial configuration.");
        }
      },

      syncTemperature: function () {
        propertySupport.invalidatingChangePreHook();
        coreModel.syncTemperature();
        propertySupport.invalidatingChangePostHook();
      },
      syncVelocity: function () {
        propertySupport.invalidatingChangePreHook();
        coreModel.syncVelocity();
        propertySupport.invalidatingChangePostHook();
      },

      addPart: function (props) {
        var WebGLOrg = model.properties.use_WebGL;

        // This will update CPU array.
        model.properties.use_WebGL = false;

        props = validator.validateCompleteness(metadata.part, props);

        propertySupport.invalidatingChangePreHook();

        coreModel.addPart(props);
        updatePartsViewModel();

        propertySupport.invalidatingChangePostHook();

        // Restore original WebGL option value. It will
        // copy CPU arrays to GPU in case of need.
        model.properties.use_WebGL = WebGLOrg;
        dispatch.partsChanged();
      },

      removePart: function (i) {
        var WebGLOrg = model.properties.use_WebGL;

        // This will update CPU array.
        model.properties.use_WebGL = false;

        propertySupport.invalidatingChangePreHook();

        coreModel.removePart(i);
        updatePartsViewModel();

        propertySupport.invalidatingChangePostHook();

        // Restore original WebGL option value. It will
        // copy CPU arrays to GPU in case of need.
        model.properties.use_WebGL = WebGLOrg;
        dispatch.partsChanged();
      },

      // Beware. The "reset" button in Lab interactives do not call this method. Instead they "reload"
      // the model, discarding this model object and creating a new one from the model JSON.
      reset: function() {
        dispatch.willReset();
        propertySupport.invalidatingChangePreHook();

        model.stop();
        coreModel.reset();

        var parts;
        // Validate parts before passing options to coreModel.
        if (initialProperties.structure && initialProperties.structure.part) {
          parts = validateParts(initialProperties.structure.part);
        }
        coreModel = coremodel.makeCoreModel(model.properties, parts);
        setWebGLEnabled(model.properties.use_WebGL);
        if (initialProperties.sensors) {
          createSensors(initialProperties.sensors);
        }
        updatePartsViewModel();
        updateSensorViewModel();

        propertySupport.invalidatingChangePostHook();
        model.resetAllOutputProperties();
        dispatch.reset();
        dispatch.invalidation();
      },

      stepCounter: function () {
        return coreModel.getIndexOfStep();
      },
      isNewStep: function () {
        return true;
      },
      stepBack: function (num) {
        return coreModel.getIndexOfStep();
      },
      stepForward: function (num) {
        if (!arguments.length) { num = 1; }
        if (!this.isStopped()) {
          this.stop();
        }
        var i=-1; while(++i < num) {
          model.tick();
        }
        return coreModel.getIndexOfStep();
      },
      getTime: function () {
        return model.properties.timeStep * coreModel.getIndexOfStep();
      },
      isWebGLActive: function () {
        return coreModel.isWebGLActive();
      },
      isWebGLCompatible: function() {
        return coreModel.isWebGLCompatible();
      },
      getWebGLError: function () {
        return coreModel.getWebGLError();
      },
      getIndexOfStep: function () {
        return coreModel.getIndexOfStep();
      },
      getTemperatureAt: function (x, y) {
        return coreModel.getTemperatureAt(x, y);
      },
      getAverageTemperatureAt: function (x, y) {
        return coreModel.getAverageTemperatureAt(x, y);
      },
      getVorticityAt: function (x, y) {
        return coreModel.getVorticityAt(x, y);
      },
      getHeatFluxAt: function (x, y) {
        return coreModel.getHeatFluxAt(x, y);
      },
      getAverageVorticityAt: function (x, y) {
        return coreModel.getAverageVorticityAt(x, y);
      },
      getSpeedAt: function (x, y) {
        return coreModel.getSpeedAt(x, y);
      },
      getTemperatureArray: function () {
        return coreModel.getTemperatureArray();
      },
      getTemperatureTexture: function () {
        return coreModel.getTemperatureTexture();
      },
      getUVelocityArray: function () {
        return coreModel.getUVelocityArray();
      },
      getVVelocityArray: function () {
        return coreModel.getVVelocityArray();
      },
      getVelocityTexture: function () {
        return coreModel.getVelocityTexture();
      },
      getPhotonsArray: function () {
        return coreModel.getPhotonsArray();
      },

      getPartsArray: function () {
        return viewModel.parts;
      },

      getSensorsArray: function () {
        return viewModel.sensors;
      },

      serialize: function () {
        var propCopy = {},
            rawProperties = propertySupport.rawValues;

        propCopy = serialize(metadata.mainProperties, rawProperties);
        propCopy.viewOptions = serialize(metadata.viewOptions, rawProperties);

        propCopy.structure = {
          part: []
        };
        viewModel.parts.forEach(function (p) {
          propCopy.structure.part.push(serialize(metadata.part, p));
        });

        propCopy.sensors = [];
        viewModel.sensors.forEach(function (s) {
          propCopy.sensors.push(serialize(metadata.sensor, s));
        });

        return propCopy;
      }
    };

    (function () {
      var parts;

      labModelerMixin.mixInto(model);
      dispatch.addEventTypes("tick", "partsChanged", "sensorsChanged");

      // Validate parts before passing options to coreModel.
      if (initialProperties.structure && initialProperties.structure.part) {
        parts = validateParts(initialProperties.structure.part);
      }

      coreModel = coremodel.makeCoreModel(model.properties, parts);
      setWebGLEnabled(model.properties.use_WebGL);

      if (initialProperties.sensors) {
        createSensors(initialProperties.sensors);
      }

      updatePartsViewModel();
      updateSensorViewModel();

      // FIXME. More yuck: We still need a pattern for recompute model properties which don't depend
      // on physics (and which therefore can be recomputed without invalidating and recomputing all
      // the physics based properties) while still making them (1) observable and (2) read-only.

      // used to triggers recomputation of isPlayable property based on isStopped and isReady:
      model.on('play.model', recomputeProperties);
      model.on('stop.model', recomputeProperties);

      function recomputeProperties() {
        propertySupport.invalidatingChangePreHook();
        propertySupport.invalidatingChangePostHook();
      }

      // Temporal workaround. In fact width and height should
      // be outputs based on min / max.
      model.defineOutput('minX', {}, function() {
        return 0;
      });
      model.defineOutput('minY', {}, function() {
        return 0;
      });
      model.defineOutput('maxX', {}, function() {
        return model.properties.model_width;
      });
      model.defineOutput('maxY', {}, function() {
        return model.properties.model_height;
      });

      model.defineOutput('time', {
        label: "Time",
        unitType: 'time',
        format: '.2f'
      }, function() {
        return model.getTime();
      });

      model.defineOutput('displayTime', {
        label: "Time"
      }, (function() {
        var f = d3.format("02d");
        return function() {
          var time = model.getTime(),
              seconds, minutes, hours, days;
          time = Math.floor(time);
          seconds = time % 60;
          time = Math.floor(time / 60);
          minutes = time % 60;
          time = Math.floor(time / 60);
          hours = time % 24;
          time = Math.floor(time / 24);
          days = time;
          return days + ':' + f(hours) + ':' + f(minutes)  + ':' + f(seconds);
        };
      }()));
    }());

    return model;
  };
});

/*jslint indent: 2, browser: true, newcap: true */
/*globals define: false*/


define('models/energy2d/views/helpers',[],function () {
  'use strict';

  // Return module with functions.
  return {
    // HSV to RGB color conversion.
    //
    // H runs from 0 to 360 degrees,
    // S and V run from 0 to 100.
    // 
    // Ported from the excellent java algorithm by Eugene Vishnevsky at:
    // http://www.cs.rit.edu/~ncs/color/t_convert.html
    // http://snipplr.com/view.php?codeview&id=14590
    HSVToRGB: function (h, s, v) {
      var
        r, g, b,
        i,
        f, p, q, t;

      // Make sure our arguments stay in-range
      h = Math.max(0, Math.min(360, h));
      s = Math.max(0, Math.min(100, s));
      v = Math.max(0, Math.min(100, v));

      // We accept saturation and value arguments from 0 to 100 because that's
      // how Photoshop represents those values. Internally, however, the
      // saturation and value are calculated from a range of 0 to 1. We make
      // That conversion here.
      s /= 100;
      v /= 100;

      if (s === 0) {
        // Achromatic (grey)
        r = g = b = v;
        return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
      }

      h /= 60; // sector 0 to 5
      i = Math.floor(h);
      f = h - i; // factorial part of h
      p = v * (1 - s);
      q = v * (1 - s * f);
      t = v * (1 - s * (1 - f));

      switch (i) {
      case 0:
        r = v;
        g = t;
        b = p;
        break;

      case 1:
        r = q;
        g = v;
        b = p;
        break;

      case 2:
        r = p;
        g = v;
        b = t;
        break;

      case 3:
        r = p;
        g = q;
        b = v;
        break;

      case 4:
        r = t;
        g = p;
        b = v;
        break;

      default: // case 5:
        r = v;
        g = p;
        b = q;
      }

      return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
    }
  };
});

/*jslint indent: 2, browser: true, newcap: true, sub: true */
/*globals define: false, $: false*/

define('models/energy2d/views/color-palette',['require','models/energy2d/views/helpers'],function (require) {
  'use strict';
  var
    // Dependencies.
    view_helpers = require('models/energy2d/views/helpers'),

    // Object with available color palettes.
    color_palette,
    // Constructor function.
    ColorPalette;

  // Setup basic color palettes.
  color_palette = {};
  color_palette['0'] = color_palette['RAINBOW']  = [[ 0, 0, 128 ], [ 20, 50, 120 ], [ 20, 100, 200 ], [ 10, 150, 150 ], [ 120, 180, 50 ], [ 220, 200, 10 ], [ 240, 160, 36 ], [ 225, 50, 50 ], [ 230, 85, 110 ], [ 250, 250, 250 ], [ 255, 255, 255 ] ];
  color_palette['1'] = color_palette['IRON']     = [ [ 40, 20, 100 ], [ 80, 20, 150 ], [ 150, 20, 150 ], [ 200, 50, 120 ], [ 220, 80, 80 ], [ 230, 120, 30 ], [ 240, 200, 20 ], [ 240, 220, 80 ], [ 255, 255, 125 ], [ 250, 250, 250 ], [ 255, 255, 255 ] ];
  color_palette['2'] = color_palette['GRAY']     = [ [ 50, 50, 50 ], [ 75, 75, 75 ], [ 100, 100, 100 ], [ 125, 125, 125 ], [ 150, 150, 150 ], [ 175, 175, 175 ], [ 200, 200, 200 ], [ 225, 225, 225 ], [ 250, 250, 250 ], [ 255, 255, 255 ] ];
  color_palette['3'] = color_palette['RAINBOW2'] = (function () {
    var
      HSVToRGB = view_helpers.HSVToRGB,
      length = 256,
      rgb = new Array(length),
      i;

    for (i = 0; i < length; i += 1) {
      rgb[i] = view_helpers.HSVToRGB(length - 1 - i, 100, 90);
    }
    return rgb;
  }());

  ColorPalette = function (color_palette_id) {
    if (color_palette_id === undefined || color_palette_id === 'DEFAULT') {
      color_palette_id = 'RAINBOW';
    }
    this.color_palette_id = color_palette_id;
  };

  ColorPalette.prototype.getRGBArray = function () {
    if (color_palette[this.color_palette_id] !== undefined) {
      return color_palette[this.color_palette_id];
    }
  };

  // Export constructor function.
  return ColorPalette;
});

/*global define: false, $: false*/

// Heatmap view.
//
// It uses HTML5 Canvas for rendering.
// getHTMLElement() returns jQuery object with the canvas used for rendering.
// Before use, this view should be bound with a heatmap using bindHeapmap(heatmap, grid_width, grid_height).
// To render the heatmap use renderHeatmap() method.
// Set size of the heatmap using CSS rules. The view fits canvas dimensions to the real
// size of the HTML element to avoid low quality CSS scaling *ONLY* when HQ rendering is enabled.
// Otherwise, the canvas has the same dimensions as heatmap grid and fast CSS scaling is used.

define('models/energy2d/views/heatmap',['require','models/energy2d/views/color-palette'],function (require) {
  'use strict';
  var
    // Dependencies.
    ColorPalette = require('models/energy2d/views/color-palette');

  return function HeatmapView(html_id) {
    var
      DEFAULT_ID = 'energy2d-heatmap-view',

      $heatmap_canvas,
      canvas_ctx,

      rgb_array,
      max_rgb_idx,

      heatmap,
      grid_width,
      grid_height,
      min_temp = 0,
      max_temp = 50,

      //
      // Private methods.
      //
      initHTMLelement = function () {
        $heatmap_canvas = $('<canvas />');
        $heatmap_canvas.attr('id', html_id || DEFAULT_ID);
        canvas_ctx = $heatmap_canvas[0].getContext('2d');
      },

      //
      // Public API.
      //
      heatmap_view = {
        // Render heat map on the canvas.
        renderHeatmap: function () {
          var
            scale, rgb_idx, val, color1, color2,
            image_data, data,
            i, j, iny, pix_index, pix_stride;

          if (!heatmap) {
            throw new Error("Heatmap: bind heatmap before rendering.");
          }

          canvas_ctx.clearRect(0, 0, grid_width, grid_height);
          // TODO: is it really necessary?
          canvas_ctx.fillStyle = "rgb(0,0,0)";

          scale = max_rgb_idx / Math.max(1, max_temp - min_temp);
          image_data = canvas_ctx.getImageData(0, 0, grid_width, grid_height);
          data = image_data.data;

          pix_index = 0;
          pix_stride = 4 * grid_width;
          for (i = 0; i < grid_width; i += 1) {
            iny = i * grid_height;
            pix_index = 4 * i;
            for (j = 0; j < grid_height; j += 1) {
              val = scale * (heatmap[iny + j] - min_temp);
              rgb_idx = Math.floor(val);
              // Get fractional part of val.
              val -= rgb_idx;
              if (rgb_idx < 0) {
                rgb_idx = 0;
                val = 0;
              } else if (rgb_idx > max_rgb_idx - 1) {
                rgb_idx = max_rgb_idx - 1;
                val = 1;
              }
              color1 = rgb_array[rgb_idx];
              color2 = rgb_array[rgb_idx + 1];
              data[pix_index]     = color1[0] * (1 - val) + color2[0] * val;
              data[pix_index + 1] = color1[1] * (1 - val) + color2[1] * val;
              data[pix_index + 2] = color1[2] * (1 - val) + color2[2] * val;
              data[pix_index + 3] = 255;
              pix_index += pix_stride;
            }
          }
          canvas_ctx.putImageData(image_data, 0, 0);
        },

        // Bind heatmap to the view.
        bindHeatmap: function (new_heatmap, new_grid_width, new_grid_height) {
          if (new_grid_width * new_grid_height !== new_heatmap.length) {
            throw new Error("Heatmap: provided heatmap has wrong dimensions.");
          }
          heatmap = new_heatmap;
          grid_width = new_grid_width;
          grid_height = new_grid_height;
          this.setCanvasSize(grid_width, grid_height);
        },

        getHTMLElement: function () {
          return $heatmap_canvas;
        },

        resize: function () {
          // Don't do anything, canvas width and height depend on  grid
          // dimensions, not CSS dimensions of canvas itself.
          // If grid is 100x100, but CSS dimensions are 500x500, CSS scaling
          // will be used.
        },

        setCanvasSize: function (w, h) {
          $heatmap_canvas.attr('width',  w);
          $heatmap_canvas.attr('height', h);
        },

        setMinTemperature: function (v) {
          min_temp = v;
        },
        setMaxTemperature: function (v) {
          max_temp = v;
        },
        setColorPalette: function (id) {
          rgb_array = new ColorPalette(id).getRGBArray();
          max_rgb_idx = rgb_array.length - 1;
        }
      };
    // One-off initialization.
    // Set the default color palette.
    heatmap_view.setColorPalette('DEFAULT');

    initHTMLelement();

    return heatmap_view;
  };
});


define('text!models/energy2d/views/heatmap-webgl-glsl/basic.vs.glsl',[],function () { return 'varying vec2 coord;\n\nvoid main() {\n  coord = gl_TexCoord.xy;\n  gl_Position = vec4(gl_Vertex.xyz, 1.0);\n}\n';});


define('text!models/energy2d/views/heatmap-webgl-glsl/temp-renderer.fs.glsl',[],function () { return '// Provided textur contains temperature data in R channel.\nuniform sampler2D heatmap_tex;\nuniform sampler2D palette_tex;\n\nuniform float max_temp;\nuniform float min_temp;\n\nvarying vec2 coord;\n\nvoid main() {\n  float temp = texture2D(heatmap_tex, coord).r;\n  float scaled_temp = (temp - min_temp) / max(1.0, max_temp - min_temp);\n  gl_FragColor = texture2D(palette_tex, vec2(scaled_temp, 0.5));\n}\n';});

/*jslint indent: 2, browser: true, newcap: true */
/*globals define: false, Uint8Array: false, $: false */

// Heatmap WebGL view.
//
// It uses HTML5 Canvas and WebGL for rendering.
// getHTMLElement() returns jQuery object with the canvas used for rendering.
// Before use, this view should be bound with a heatmap texture using bindHeapmapTexture(heatmap_tex).
// To render the heatmap use renderHeatmapTexture() method.
// Set size of the heatmap using CSS rules.
define('models/energy2d/views/heatmap-webgl',['require','models/energy2d/gpu/context','models/energy2d/gpu/texture','models/energy2d/gpu/shader','models/energy2d/gpu/mesh','models/energy2d/views/color-palette','text!models/energy2d/views/heatmap-webgl-glsl/basic.vs.glsl','text!models/energy2d/views/heatmap-webgl-glsl/temp-renderer.fs.glsl'],function (require) {
  'use strict';
  var
    // Dependencies.
    context      = require('models/energy2d/gpu/context'),
    Texture      = require('models/energy2d/gpu/texture'),
    Shader       = require('models/energy2d/gpu/shader'),
    Mesh         = require('models/energy2d/gpu/mesh'),
    ColorPalette = require('models/energy2d/views/color-palette'),
    // Shader sources.
    basic_vs         = require('text!models/energy2d/views/heatmap-webgl-glsl/basic.vs.glsl'),
    temp_renderer_fs = require('text!models/energy2d/views/heatmap-webgl-glsl/temp-renderer.fs.glsl');

  return function HeatmapWebGLView(html_id) {
    var
      // Get WebGL context.
      gl = context.getWebGLContext(),
      // GLSL Render program.
      render_program = new Shader(basic_vs, temp_renderer_fs),
      // Plane used for rendering.
      plane = Mesh.plane({ coords: true }),
      // Color palette texture (init later).
      palette_tex,

      DEFAULT_ID = 'energy2d-heatmap-webgl-view',

      $heatmap_canvas,
      canvas_width,
      canvas_height,

      heatmap_tex,
      min_temp = 0,
      max_temp = 50,

      //
      // Private methods.
      //
      initHTMLelement = function () {
        $heatmap_canvas = $(gl.canvas);
        $heatmap_canvas.attr('id', html_id || DEFAULT_ID);
      },

      // Make sure that no FBO is bound and viewport has proper dimensions
      // (it's not obvious as this context is also used for GPGPU calculations).
      setupRenderTarget = function () {
        // Ensure that FBO is null, as GPGPU operations which use FBOs also take place.
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        // This is necessary, as GPGPU operations can modify viewport size.
        gl.viewport(0, 0, canvas_width, canvas_height);
      },

      //
      // Public API.
      //
      heatmap_view = {
        // Render heat map on the canvas.
        renderHeatmap: function () {

          if (!heatmap_tex) {
            throw new Error("Heatmap: bind heatmap texture before rendering.");
          }

          setupRenderTarget();

          gl.clear(gl.COLOR_BUFFER_BIT);
          heatmap_tex.bind(0);
          palette_tex.bind(1);
          render_program.draw(plane);
          palette_tex.unbind(1);
          heatmap_tex.unbind(0);
        },

        resize: function () {
          canvas_width = $heatmap_canvas.width();
          canvas_height = $heatmap_canvas.height();
          $heatmap_canvas.attr('width', canvas_width);
          $heatmap_canvas.attr('height', canvas_height);
        },

        // Bind heatmap to the view.
        bindHeatmapTexture: function (new_heatmap_tex) {
          heatmap_tex = new_heatmap_tex;
        },

        getHTMLElement: function () {
          return $heatmap_canvas;
        },

        setMinTemperature: function (v) {
          min_temp = v;
          render_program.uniforms({
            min_temp: min_temp
          });
        },
        setMaxTemperature: function (v) {
          max_temp = v;
          render_program.uniforms({
            max_temp: max_temp
          });
        },
        setColorPalette: function (id) {
          var rgb_array, len, tex_data, i, i4;
          rgb_array = new ColorPalette(id).getRGBArray();
          len = rgb_array.length;
          tex_data = new Uint8Array(len * 4);
          for (i = 0; i < len; i += 1) {
            i4 = i * 4;
            tex_data[i4]     = rgb_array[i][0];
            tex_data[i4 + 1] = rgb_array[i][1];
            tex_data[i4 + 2] = rgb_array[i][2];
            tex_data[i4 + 3] = 255;
          }
          palette_tex = new Texture(len, 1, { type: gl.UNSIGNED_BYTE, format: gl.RGBA, filter: gl.LINEAR });
          gl.bindTexture(gl.TEXTURE_2D, palette_tex.id);
          gl.texImage2D(gl.TEXTURE_2D, 0, palette_tex.format, len, 1, 0, palette_tex.format, palette_tex.type, tex_data);
        }
      };

    // One-off initialization.
    // Set the default color palette.
    heatmap_view.setColorPalette('DEFAULT');
    // Set render program uniforms.
    render_program.uniforms({
      // Texture units.
      heatmap_tex: 0,
      palette_tex: 1,
      // Uniforms.
      min_temp: min_temp,
      max_temp: max_temp
    });
    // Setup texture coordinates.
    plane.coords = [[1, 0], [1, 1], [0, 0], [0, 1]];
    // Update buffers.
    plane.compile();

    initHTMLelement();

    return heatmap_view;
  };
});

/*global define: false, $: false*/

// WebGL Status.
// Presents available WebGL features.
//
// getHTMLElement() method returns JQuery object with DIV that contains status.

define('models/energy2d/views/webgl-status',['require','common/controllers/checkbox-controller','models/energy2d/gpu/gpgpu'],function (require) {
  'use strict';
  var
    // Dependencies.
    CheckboxController = require('common/controllers/checkbox-controller'),
    gpgpu              = require('models/energy2d/gpu/gpgpu'),

    GET_WEBGL = '<p><a href="http://get.webgl.org" target="_blank">Click to learn more about WebGL.</a></p>';

  return function WebGLStatusView(html_id, model) {
    var
      DEFAULT_ID = 'e2d-webgl-status',
      $div,
      $webgl_icon,
      $status_wrapper,
      $status,
      webgl_checkbox,
      $checkbox,
      energy2d_modeler,
      WebGL_status_view,
      interactiveControllerProxy = {
        getModel: function () { return model; },
        getScriptingAPI: function () { return function () { }; },
        getNextTabIndex: function () { return function () { }; }
      };

    //
    // Private methods.
    //

    function initHTMLelement() {
      var $closeBtn;

      $div = $('<div />');
      $div.attr('id', html_id || DEFAULT_ID);
      $status_wrapper = $('<div id="status-wrapper"/>');
      $status_wrapper.appendTo($div);
      $status = $('<div />');
      $status.appendTo($status_wrapper);

      $closeBtn = $('<a id="hide-webgl-status" class="button"><i class="icon-remove"></i></a>');
      $closeBtn.on('click', hide);
      $closeBtn.appendTo($status_wrapper);

      $checkbox.appendTo($status_wrapper);

      $webgl_icon = $('<a id="show-webgl-status" class="button"><i class="icon-bolt"></i></a>');
      $webgl_icon.on('click', show);
      $webgl_icon.appendTo($div);

      $status_wrapper.hide();
    }

    function show() {
      $webgl_icon.hide();
      $status_wrapper.fadeIn();
    }

    function hide() {
      $webgl_icon.fadeIn();
      $status_wrapper.fadeOut();
    }

    function initialize() {
      // Second argument to new CheckboxController replicates part of an
      // interactiveController to generate the necessary API a checkbox
      // controller component needs
      webgl_checkbox = new CheckboxController({
        "type": "checkbox",
        "id": "webgl-status-checkbox",
        "text": "WebGL-accelerated physics engine",
        "property": "use_WebGL"
      }, interactiveControllerProxy);
      $checkbox = webgl_checkbox.getViewContainer();
      initHTMLelement();
    }

    //
    // Public API.
    //
    WebGL_status_view = {
      bindModel: function (model) {
        energy2d_modeler = model;
        interactiveControllerProxy.getModel = function () { return model; };
        webgl_checkbox.modelLoadedCallback(interactiveControllerProxy);
      },

      render: function () {
        var modelCompatible = energy2d_modeler.isWebGLCompatible(),
            feature = gpgpu.featuresInfo,
            requiredFeatures = true,
            optionalFeatures = true,
            initError = energy2d_modeler.properties.use_WebGL && !energy2d_modeler.isWebGLActive(),
            content;

        $status.empty();

        Object.keys(feature).forEach(function (name) {
          var f = feature[name];
          if (f.required && !f.available) {
            requiredFeatures = false;
          }
          if (!f.required && !f.available) {
            optionalFeatures = false;
          }
        });

        // WebGL + required extensions availability message.
        if (requiredFeatures && optionalFeatures) {
          $status.append('<p>Your browser <span class="happy">supports</span> WebGL as well as all required and optional extensions!</p>');
        } else if (requiredFeatures) {
          $status.append('<p>Your browser <span class="happy">supports</span> WebGL and all required extensions! However some ' +
                         'optional extensions are unavailable:</p>');
          Object.keys(feature).forEach(function (name) {
            var f = feature[name],
                supported;
            if (!f.required) {
              supported = f.available ? '<span class="happy"><i class="icon-ok"></i></span>' : '<span class="sad"><i class="icon-remove"></i></span>';
              $status.append('<p class="extension">' + supported + ' ' + name + '</p>');
            }
          });
          $status.append('<p>WebGL rendering quality can be affected.</p>');
        } else if (feature['WebGL']) {
          $status.append('<p>Your browser <span class="happy">supports</span> WebGL, however not all required extensions are available:</p>');
          Object.keys(feature).forEach(function (name) {
            var f = feature[name],
                supported;
            if (f.required) {
              supported = f.available ? '<span class="happy"><i class="icon-ok"></i></span>' : '<span class="sad"><i class="icon-remove"></i></span>';
              $status.append('<p class="extension">' + supported + ' ' + name + '</p>');
            }
          });
          $status.append(GET_WEBGL);
        } else {
          $status.append('<p>Sorry, your browser <span class="sad">does not support</span> WebGL.');
          $status.append(GET_WEBGL);
        }

        // Model compatibility message.
        if (modelCompatible) {
          content = 'This model is <span class="happy">compatible</span> with WebGL-accelerated physics engine';
          if (energy2d_modeler.isWebGLActive()) {
            content += ' and it is <span class="happy">active</span>.';
          } else {
            content += ', but it is <span class="sad">inactive</span>.';
            if (initError) {
              content += ' Unfortunately, its initialization <span class="sad">failed</span>. Check the browser console for details.';
            } else if (requiredFeatures) {
              content += ' Enable it to speed up simulation:';
            }
          }
          $status.append('<p>' + content + '</p>');
        } else {
          $status.append('<p>Unfortunately, some features used in this model are <span class="sad">incompatible</span> ' +
                         'with WebGL-accelerated physics engine.</p>');
        }

        // WebGL solvers checkbox.
        if (!requiredFeatures || !modelCompatible || initError) {
          // If any test failed hide the checkbox.
          $checkbox.hide();
        } else {
          $checkbox.show();
        }

        // WebGL icon tooltip message and color.
        if (initError) {
          content = 'WebGL initialization failed.';
          $webgl_icon.removeClass("happy");
          $webgl_icon.addClass("sad");
        } else if (!requiredFeatures || !modelCompatible) {
          content = 'WebGL unavailable.';
          $webgl_icon.removeClass("happy");
          $webgl_icon.addClass("sad");
        } else if (!energy2d_modeler.isWebGLActive()) {
          content = 'WebGL available, but inactive.';
          $webgl_icon.removeClass("happy");
          $webgl_icon.removeClass("sad");
        } else {
          content = 'WebGL available and active.';
          $webgl_icon.removeClass("sad");
          $webgl_icon.addClass("happy");
        }
        content += ' Click for detailed information.';
        $webgl_icon.attr('title', content);

        if (initError) {
          // Display panel when user requested WebGL, but it wasn't
          // initialized correctly.
          show();
        }
      },

      getHTMLElement: function () {
        return $div;
      }
    };

    // One-off initialization.
    initialize();

    return WebGL_status_view;
  };
});

/*jslint indent: 2, browser: true, newcap: true */
/*globals define: false, $: false*/

// Vector map view.
//
// It uses HTML5 Canvas for rendering.
// getHTMLElement() returns jQuery object with canvas used for rendering.
// Before use, this view should be bound with the vector map using bindVectormap(vectormap_u, vectormap_v, width, height, spacing).
// To render vector map use renderVectormap() method.
// Set size of the vectormap using CSS rules. The view fits canvas dimensions to the real
// size of the HTML element to avoid low quality CSS scaling.

define('models/energy2d/views/vectormap',[],function () {
  'use strict';

  return function VectormapView(html_id) {
    var
      DEFAULT_ID = 'energy2d-vectormap-view',
      VECTOR_SCALE = 100,
      VECTOR_BASE_LEN = 8,
      WING_COS = Math.cos(0.523598776),
      WING_SIN = Math.sin(0.523598776),
      WING_LEN = 4,
      ARROW_COLOR = "rgb(175,175,175)",

      $vectormap_canvas,
      canvas_ctx,
      canvas_width,
      canvas_height,

      vectormap_u,
      vectormap_v,
      grid_width,
      grid_height,
      spacing,

      enabled = true,

      //
      // Private methods.
      //
      initHTMLelement = function () {
        $vectormap_canvas = $('<canvas />');
        $vectormap_canvas.attr('id', html_id || DEFAULT_ID);
        canvas_ctx = $vectormap_canvas[0].getContext('2d');
      },

      // Helper method for drawing a single vector.
      drawVector = function (x, y, vx, vy) {
        var
          r = 1.0 / Math.sqrt(vx * vx + vy * vy),
          arrowx = vx * r,
          arrowy = vy * r,
          x1 = x + arrowx * VECTOR_BASE_LEN + vx * VECTOR_SCALE,
          y1 = y + arrowy * VECTOR_BASE_LEN + vy * VECTOR_SCALE,
          wingx = WING_LEN * (arrowx * WING_COS + arrowy * WING_SIN),
          wingy = WING_LEN * (arrowy * WING_COS - arrowx * WING_SIN);

        canvas_ctx.beginPath();
        canvas_ctx.moveTo(x, y);
        canvas_ctx.lineTo(x1, y1);

        canvas_ctx.lineTo(x1 - wingx, y1 - wingy);
        canvas_ctx.moveTo(x1, y1);

        wingx = WING_LEN * (arrowx * WING_COS - arrowy * WING_SIN);
        wingy = WING_LEN * (arrowy * WING_COS + arrowx * WING_SIN);
        canvas_ctx.lineTo(x1 - wingx, y1 - wingy);

        canvas_ctx.stroke();
      },

      //
      // Public API.
      //
      vectormap_view = {
        // Render vectormap on the canvas.
        renderVectormap: function () {
          if (!enabled) return;

          var
            dx, dy, x0, y0, uij, vij,
            i, j, iny, ijny;

          if (!vectormap_u || !vectormap_v) {
            throw new Error("Vectormap: bind vectormap before rendering.");
          }

          dx = canvas_width / grid_width;
          dy = canvas_height / grid_height;

          canvas_ctx.clearRect(0, 0, canvas_width, canvas_height);
          canvas_ctx.strokeStyle = ARROW_COLOR;
          canvas_ctx.lineWidth = 1;

          for (i = 1; i < grid_width - 1; i += spacing) {
            iny = i * grid_height;
            x0 = (i + 0.5) * dx; // + 0.5 to move arrow into field center
            for (j = 1; j < grid_height - 1; j += spacing) {
              ijny = iny + j;
              y0 = (j + 0.5) * dy; // + 0.5 to move arrow into field center
              uij = vectormap_u[ijny];
              vij = vectormap_v[ijny];
              if (uij * uij + vij * vij > 1e-15) {
                drawVector(x0, y0, uij, vij);
              }
            }
          }
        },

        clear: function () {
          canvas_ctx.clearRect(0, 0, canvas_width, canvas_height);
        },

        get enabled() {
          return enabled;
        },
        set enabled(v) {
          enabled = v;
          // Clear vectormap, as .renderVectormap() call won't do it.
          if (!enabled) vectormap_view.clear();
        },

        // Bind vector map to the view.
        bindVectormap: function (new_vectormap_u, new_vectormap_v, new_grid_width, new_grid_height, arrows_per_row) {
          if (new_grid_width * new_grid_height !== new_vectormap_u.length) {
            throw new Error("Heatmap: provided U component of vectormap has wrong dimensions.");
          }
          if (new_grid_width * new_grid_height !== new_vectormap_v.length) {
            throw new Error("Heatmap: provided V component of vectormap has wrong dimensions.");
          }
          vectormap_u = new_vectormap_u;
          vectormap_v = new_vectormap_v;
          grid_width = new_grid_width;
          grid_height = new_grid_height;
          spacing = Math.round(new_grid_width / arrows_per_row);
        },

        getHTMLElement: function () {
          return $vectormap_canvas;
        },

        resize: function () {
          canvas_width = $vectormap_canvas.width();
          canvas_height = $vectormap_canvas.height();
          $vectormap_canvas.attr('width', canvas_width);
          $vectormap_canvas.attr('height', canvas_height);
        }
      };

    // One-off initialization.
    initHTMLelement();

    return vectormap_view;
  };
});


define('text!models/energy2d/views/vectormap-webgl-glsl/vectormap.vs.glsl',[],function () { return '// Provided texture contains vector data in RG channels.\nattribute vec2 origin;\n\nuniform sampler2D vectormap_tex;\nuniform float base_length;\nuniform float vector_scale;\nuniform vec2 scale;\n\nvoid main() {\n  // Read vector which should be visualized.\n  vec2 vec = texture2D(vectormap_tex, gl_TexCoord.xy).xy;\n  vec.y = -vec.y;\n\n  if (length(vec) < 1e-15) {\n    // Do not draw to small vectors.\n    // Set position outside [-1, 1] region, which is rendered.\n    gl_Position = vec4(2.0);\n    return;\n  }\n\n  // Test which part of the vector arrow is being processed. \n  if (gl_Vertex.x == 0.0 && gl_Vertex.y == 0.0) {\n    // Origin of the arrow is being processed.\n    // Just transform its coordinates.\n    gl_Position = vec4(origin, 0.0, 1.0);\n  } else {\n    // Other parts of arrow are being processed.\n    // Set proper length of the arrow, rotate it, scale\n    // and finally transform.\n\n    // Calculate arrow length.\n    vec2 new_pos = gl_Vertex.xy;\n    new_pos.x += base_length + vector_scale * length(vec);\n\n    // Calculate angle between reference arrow (horizontal).\n    vec = normalize(vec);\n    float angle = acos(dot(vec, vec2(1.0, 0.0)));\n    if (vec.y < 0.0) {\n      angle = -angle;\n    }\n    // Prepare rotation matrix.\n    // See: http://en.wikipedia.org/wiki/Rotation_matrix\n    mat2 rot_m = mat2(\n      cos(angle), sin(angle),\n     -sin(angle), cos(angle)\n    );\n    // Rotate.\n    new_pos = rot_m * new_pos;\n    // Scale.\n    new_pos = new_pos * scale;\n    // Transform.\n    gl_Position = vec4(new_pos + origin, 0.0, 1.0);\n  }\n}\n';});


define('text!models/energy2d/views/vectormap-webgl-glsl/vectormap.fs.glsl',[],function () { return 'uniform vec4 color;\n\nvoid main() {\n  gl_FragColor = color;\n}\n';});

/*global define: false, $: false*/

// Vectormap WebGL view.
//
// It uses HTML5 Canvas and WebGL for rendering.
// getHTMLElement() returns jQuery object with the canvas used for rendering.
// Before use, this view should be bound with a heatmap texture using bindHeapmapTexture(vectormap_tex).
// To render the heatmap use renderVectormapTexture() method.
// Set size of the heatmap using CSS rules.

define('models/energy2d/views/vectormap-webgl',['require','models/energy2d/gpu/context','models/energy2d/gpu/shader','models/energy2d/gpu/mesh','text!models/energy2d/views/vectormap-webgl-glsl/vectormap.vs.glsl','text!models/energy2d/views/vectormap-webgl-glsl/vectormap.fs.glsl'],function (require) {
  'use strict';
  var
    // Dependencies.
    context = require('models/energy2d/gpu/context'),
    Shader  = require('models/energy2d/gpu/shader'),
    Mesh    = require('models/energy2d/gpu/mesh'),
    // Shader sources. One of Lab build steps converts sources to the JavaScript file.
    vectormap_vs = require('text!models/energy2d/views/vectormap-webgl-glsl/vectormap.vs.glsl'),
    vectormap_fs = require('text!models/energy2d/views/vectormap-webgl-glsl/vectormap.fs.glsl');

  return function VectormapWebGLView(html_id) {
    var
      // Get WebGL context.
      gl = context.getWebGLContext(),
      // GLSL Render program.
      render_program = new Shader(vectormap_vs, vectormap_fs),
      // Plane used for rendering.
      arrows = new Mesh({ coords: true, lines: true }),

      DEFAULT_ID = 'energy2d-vectormap-webgl-view',
      VECTOR_SCALE = 100,
      VECTOR_BASE_LEN = 8,
      ARROW_COLOR = [0.7, 0.7, 0.7, 1.0],

      $vectormap_canvas,
      canvas_width,
      canvas_height,

      vectormap_tex,
      grid_width,
      grid_height,
      spacing,

      enabled = true,

      //
      // Private methods.
      //
      initGeometry = function () {
        var i, j, idx, origin, coord,
          gdx = 2.0 / grid_width,
          gdy = 2.0 / grid_height,
          tdx = 1.0 / grid_width,
          tdy = 1.0 / grid_height;

        arrows.addVertexBuffer('origins', 'origin');
        arrows.vertices = [];
        arrows.origins = [];
        arrows.coords = [];
        arrows.lines = [];

        idx = 0;
        for (i = 1; i < grid_width - 1; i += spacing) {
          for (j = 1; j < grid_height - 1; j += spacing) {
            // Base arrows vertices. Origin, front and two wings. The unit is pixel.
            // Base length is 0.01 px - just for convenience (it distinguish front of the arrows from the origin).
            arrows.vertices.push([0, 0, 0], [0.01, 0, 0], [-3, 2, 0], [-3, -2, 0]);
            // All of these vertices have to know which vector they are representing.
            origin = [-1.0 + (i + 0.5) * gdx, 1.0 - (j + 0.5) * gdy, 0];
            arrows.origins.push(origin, origin, origin, origin);
            // Texture coordinates.
            coord = [(j + 0.5) * tdy, (i + 0.5) * tdx];
            arrows.coords.push(coord, coord, coord, coord);
            // Draw three lines. From origin to the fron of the arrows + two wings.
            arrows.lines.push([idx, idx + 1], [idx + 1, idx + 2], [idx + 1, idx + 3]);
            idx += 4;
          }
        }
        // Update buffers.
        arrows.compile();
      },

      initHTMLelement = function () {
        $vectormap_canvas = $(gl.canvas);
        $vectormap_canvas.attr('id', html_id || DEFAULT_ID);
      },

      // Make sure that no FBO is bound and viewport has proper dimensions
      // (it's not obvious as this context is also used for GPGPU calculations).
      setupRenderTarget = function () {
        // Ensure that FBO is null, as GPGPU operations which use FBOs also take place.
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        // This is necessary, as GPGPU operations can modify viewport size.
        gl.viewport(0, 0, canvas_width, canvas_height);
      },

      //
      // Public API.
      //
      vectormap_view = {
        // Render heat map on the canvas.
        renderVectormap: function () {
          if (!enabled) return;

          if (!vectormap_tex) {
            throw new Error("Vectormap: bind heatmap texture before rendering.");
          }

          setupRenderTarget();

          vectormap_tex.bind(0);
          render_program.draw(arrows, gl.LINES);
          vectormap_tex.unbind(0);
        },

        get enabled() {
          return enabled;
        },
        set enabled(v) {
          enabled = v;
        },

        resize: function () {
          canvas_width = $vectormap_canvas.width();
          canvas_height = $vectormap_canvas.height();
          $vectormap_canvas.attr('width', canvas_width);
          $vectormap_canvas.attr('height', canvas_height);
          // Render ara has dimensions from -1.0 to 1.0, so its width/height is 2.0.
          render_program.uniforms({
            scale: [2.0 / canvas_width, 2.0 / canvas_height]
          });
        },

        // Bind vectormap to the view.
        bindVectormapTexture: function (new_vectormap_tex, new_grid_width, new_grid_height, arrows_per_row) {
          vectormap_tex = new_vectormap_tex;
          grid_width = new_grid_width;
          grid_height = new_grid_height;
          spacing = Math.round(grid_width / arrows_per_row);

          initGeometry();
        },

        getHTMLElement: function () {
          return $vectormap_canvas;
        }
      };

    // One-off initialization.
    // Set render program uniforms.
    render_program.uniforms({
      // Texture units.
      vectormap_tex: 0,
      // Uniforms.
      base_length: VECTOR_BASE_LEN,
      vector_scale: VECTOR_SCALE,
      color: ARROW_COLOR
    });

    initHTMLelement();

    return vectormap_view;
  };
});

/*global define: false, $: false*/

// Energy2D photons view.
//
// It uses HTML5 Canvas for rendering.
// getHTMLElement() returns jQuery object with canvas used for rendering.
// Before use, this view should be bound with the parts array using bindPhotonsArray(photons).
// To render parts use renderPhotons() method.
// Set size of the parts view using CSS rules. The view fits canvas dimensions to the real
// size of the HTML element to avoid low quality scaling.

define('models/energy2d/views/photons',[],function () {
  'use strict';

  return function PhotonsView(html_id) {
    var
      DEFAULT_ID = 'energy2d-photons-view',
      DEFAULT_CLASS = 'energy2d-photons-view',

      PHOTON_LENGTH = 10,

      $photons_canvas,
      canvas_ctx,
      canvas_width,
      canvas_height,

      photons,
      scale_x,
      scale_y,
      scene_width,
      scene_height,

      //
      // Private methods.
      //
      initHTMLelement = function () {
        $photons_canvas = $('<canvas />');
        $photons_canvas.attr('id', html_id || DEFAULT_ID);
        $photons_canvas.addClass(DEFAULT_CLASS);

        canvas_ctx = $photons_canvas[0].getContext('2d');
      },

      setCanvasStyle = function () {
        canvas_ctx.strokeStyle = "rgba(255,255,255,128)";
        canvas_ctx.lineWidth = 0.5;
      },

      //
      // Public API.
      //
      photons_view = {
        // Render vectormap on the canvas.
        renderPhotons: function () {
          var
            photon, sx, sy, r,
            i, len;

          if (!photons) {
            throw new Error("Photons view: bind parts array before rendering.");
          }

          canvas_ctx.clearRect(0, 0, canvas_width, canvas_height);
          for (i = 0, len = photons.length; i < len; i += 1) {
            photon = photons[i];

            sx = photon.x * scale_x;
            sy = photon.y * scale_y;
            r = 1 / Math.sqrt(photon.vx * photon.vx + photon.vy * photon.vy);

            canvas_ctx.beginPath();
            canvas_ctx.moveTo(sx, sy);
            canvas_ctx.lineTo(sx + PHOTON_LENGTH * photon.vx * r, sy + PHOTON_LENGTH * photon.vy * r);
            canvas_ctx.stroke();
          }
        },

        // Bind vector map to the view.
        bindPhotonsArray: function (new_photons, new_scene_width, new_scene_height) {
          photons = new_photons;
          scene_width = new_scene_width;
          scene_height = new_scene_height;
          scale_x = canvas_width / scene_width;
          scale_y = canvas_height / scene_height;
        },

        getHTMLElement: function () {
          return $photons_canvas;
        },

        resize: function () {
          canvas_width = $photons_canvas.width();
          canvas_height = $photons_canvas.height();
          scale_x = canvas_width / scene_width;
          scale_y = canvas_height / scene_height;
          $photons_canvas.attr('width', canvas_width);
          $photons_canvas.attr('height', canvas_height);
          setCanvasStyle();
        }
      };

    // One-off initialization.
    initHTMLelement();
    setCanvasStyle();

    return photons_view;
  };
});
/*global define: false, d3: false */

define('models/energy2d/views/parts',[],function () {

  // Classic version of Energy2D was rendering rectangles with small shift.
  // If we do the same then converted models look better.
  var E2D_XY_SHIFT = 1,
      E2D_DIM_SHIFT = 2;

  return function PartsView(SVGContainer, g) {
    var api,
        parts,

        m2px = SVGContainer.model2px,
        m2pxInv = SVGContainer.model2pxInv,

        shapeTest = {
          "rect":    function (d) { return d.shapeType === "rectangle" ? this : null; },
          "ellipse": function (d) { return d.shapeType === "ellipse" ? this : null; },
          "path": function (d) { return d.shapeType === "polygon" || d.shapeType === "ring" ? this : null; }
        },

        ringPathSpec = d3.svg.arc()
            .innerRadius(function (d) { return m2px(d.inner * 0.5); })
            .outerRadius(function (d) { return m2px(d.outer * 0.5); })
            .startAngle(0)
            .endAngle(Math.PI * 2),

        dragBehavior = (function () {
          var x, y, minX, maxX, minY, maxY, bbox;
          return d3.behavior.drag()
              .origin(function (d) {
                return {
                  x: m2px(d.x),
                  y: m2pxInv(d.y)
                };
              })
              .on("dragstart", function (d) {
                var rx, ry;
                if (d.draggable) {
                  x = y = null;
                  rx = m2px.range();
                  ry = m2pxInv.range();
                  minX = Math.min(rx[0], rx[1]);
                  maxX = Math.max(rx[0], rx[1]);
                  minY = Math.min(ry[0], ry[1]);
                  maxY = Math.max(ry[0], ry[1]);
                  bbox = this.getBBox();
                  bbox.x0 = bbox.x + 10;
                  bbox.y0 = bbox.y + 10;
                  bbox.x1 = bbox.x + bbox.width - 10;
                  bbox.y1 = bbox.y + bbox.height - 10;
                  d3.select(this).style("opacity", 0.7);
                }
              })
              .on("drag", function (d) {
                if (d.draggable) {
                  x = d3.event.x;
                  y = d3.event.y;
                  x -= Math.max(0, bbox.x0 + x - maxX) + Math.min(0, bbox.x1 + x - minX);
                  y -= Math.max(0, bbox.y0 + y - maxY) + Math.min(0, bbox.y1 + y - minY);
                  d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
                }
              })
              .on("dragend", function (d) {
                if (d.draggable) {
                  d3.select(this).style("opacity", 1);
                  if (x !== null) { // no need to check 'y' too
                    d.x = m2px.invert(x);
                    d.y = m2pxInv.invert(y);
                  }
                }
              });
        }());

    function transform(d) { return "translate(" + m2px(d.x || 0) + "," + m2pxInv(d.y || 0) + ")"; }
    function width(d) { return m2px(d.width) + E2D_DIM_SHIFT; }
    function height(d) { return m2px(d.height) + E2D_DIM_SHIFT; }
    function rx(d) { return m2px(d.a * 0.5); }
    function ry(d) { return m2px(d.b * 0.5); }
    function visibility(d) { return d.visible ? "visible" : "hidden"; }
    function textureFill(d) { return d.texture ? "url(#texture-1)" : "none"; }
    function label(d) { return d.computeLabel(); }
    function dx() { return -this.getBBox().width / 2; }
    function fill(d) {
      if (!d.filled) return 'rgba(0, 0, 0, 0)';
      var color;
      if (d.color === "auto") {
        if (d.power > 0) {
          color = '#FFFF00';
        } else if (d.power < 0) {
          color = '#B0C4DE';
        } else if (d.constant_temperature) {
          // Heatmap will be visible.
          color = 'rgba(0, 0, 0, 0)';
        } else {
          color = "#999";
        }
      } else {
        // Typical color definition.
        color = d.color;
        // TODO: this should be done during XML->JSON conversion.
        if (!isNaN(parseInt(color, 16))) {
          while (color.length < 6) {
            color = '0' + color;
          }
          color = '#' + color;
        }
      }
      return color;
    }
    function polygonPathSpec(d) {
      var res = [],
          x = d.raw_x_coords,
          y = d.raw_y_coords,
          i, len;
      for (i = 0, len = x.length; i < len; i++) {
        res.push(m2px(x[i]));
        res.push(m2pxInv(y[i]));
      }
      return "M" + res.join(",") + "Z";
    }
    function pathSpec(d) {
      switch (d.shapeType ) {
      case "polygon":
        return polygonPathSpec(d);
      case "ring":
        return ringPathSpec(d);
      }
    }
    function xLabel (d) {
      var s = d.shapeType;
      return s === "rectangle" || s === "polygon" ? this.parentNode.firstElementChild.getBBox().width / 2 : 0;
    }
    function yLabel (d) {
      var s = d.shapeType;
      return s === "rectangle" || s === "polygon" ? this.parentNode.firstElementChild.getBBox().height / 2 : 0;
    }

    function generateTextures() {
      var p = g.append("defs").append("pattern")
          .attr("id", "texture-1")
          .attr("patternUnits", "userSpaceOnUse")
          .attr("x", 0)
          .attr("y", 0)
          .attr("width", "0.7em")
          .attr("height", "0.7em")
          .attr("viewBox", "0 0 16 16");
      p.append("path")
          .attr("class", "e2d-texture-path-shadow")
          .attr("d", "M0,0 L16,16 M-1,15 L1,17 M15,-1 L17,1");
      p.append("path")
          .attr("class", "e2d-texture-path")
          .attr("d", "M0,0 L16,16 M-1,15 L1,17 M15,-1 L17,1");
    }

    function renderShape(shape, enter, update) {
      enter = enter.select(shapeTest[shape]);
      enter.append(shape)
          .attr("class", "e2d-part-shape");
      enter.append(shape)
          .attr("class", "e2d-part-shape-outline")
          .attr("fill", textureFill);

      // Propagate data.
      update.select(".e2d-part-shape");
      update.select(".e2d-part-shape-outline");

      switch(shape) {
      case "rect":
        update.selectAll("rect")
            .attr("x", E2D_XY_SHIFT)
            .attr("y", E2D_XY_SHIFT)
            .attr("width", width)
            .attr("height", height);
        break;
      case "ellipse":
        update.selectAll("ellipse")
            .attr("rx", rx)
            .attr("ry", ry);
        break;
      case "path":
        update.selectAll("path")
            .attr("d", pathSpec);
        break;
      }
    }

    function renderLabels(enter, update) {
      enter.append("text")
          .attr("class", "e2d-part-label-shadow")
          .attr("dy", ".35em");
      enter.append("text")
          .attr("class", "e2d-part-label")
          .attr("dy", ".35em");

      // Propagate data.
      update.select(".e2d-part-label");
      update.select(".e2d-part-label-shadow");
      update.selectAll(".e2d-part-label, .e2d-part-label-shadow")
          .text(label)
          .attr("dx", dx)
          .attr("x", xLabel)
          .attr("y", yLabel);
    }

    // Public API.
    api = {
      renderParts: function () {
        if (!parts) return;

        var part, partEnter;

        part = g.selectAll(".e2d-part").data(parts);
        partEnter = part.enter().append("g")
            // "part" class is useful for onClick handles, so author
            // can call: onClick("part", function () { ... }).
            .attr("class", "e2d-part part");

        renderShape("rect", partEnter, part);
        renderShape("ellipse", partEnter, part);
        renderShape("path", partEnter, part);
        renderLabels(partEnter, part);

        partEnter.call(dragBehavior);

        part
            .attr("transform", transform);
        part.select(".e2d-part-shape")
            .attr("fill", fill)
            .style("visibility", visibility);

        part.exit().remove();
      },

      bindPartsArray: function (newParts) {
        parts = newParts;
      }
    };

    (function () {
      generateTextures();
    }());

    return api;
  };
});

/*global define: false, d3: false */

define('models/energy2d/views/sensors',[],function () {
  var TH_W = 2,
      TH_H = 4;

  return function SensorsView(SVGContainer, g) {
    var api,

        m2px = SVGContainer.model2px,
        m2pxInv = SVGContainer.model2pxInv,

        sensors,

        thermBg, // d3.selection
        thermReading, // d3.selection
        thermValScale = d3.scale.linear().clamp(true).domain([0, 50]).range([TH_H, 0]),

        anemoRot, // d3.selection

        heatFluxReading, // d3.selection

        dragBehavior = d3.behavior.drag()
            .origin(function (d) {
                return {
                  x: m2px(d.x),
                  y: m2pxInv(d.y)
                };
              })
            .on("drag", function (d) {
              d.x = m2px.invert(d3.event.x);
              d.y = m2pxInv.invert(d3.event.y);
            });

    function em(val) { return val + "%"; }
    function transform(d) { return "translate(" + m2px(d.x) + "," + m2pxInv(d.y) + ")"; }
    function labelDx() { return -this.getBBox().width / 2; }
    function labelText(d) { return d.label; }
    function readingText(d) { return d.value.toFixed(1) + " °C"; }
    function bgHeight(d) { return em(thermValScale(d.value)); }

    function anemometerRotation(d) { return "rotate(" + d.value + ")"; }

    function heatFluxReadingText(d) { return d.value.toFixed(1) + " W/m²"; }
    function heatFluxRot(d) { return "rotate(" + d.angle + ")"; }

    function measuringPoint(g) {
      g = g.append("g").attr("class", "e2d-measuring-point");
      g.append("line")
          .attr("x1", 0)
          .attr("y1", "-0.8%")
          .attr("x2", 0)
          .attr("y2", "0.8%");
      g.append("line")
          .attr("x1", "-0.8%")
          .attr("y1", 0)
          .attr("x2", "0.8%")
          .attr("y2", 0);
      g.append("circle")
          .attr("r", "0.8%");
    }

    function supportLabels(enter, update) {
      enter.append("text").attr("class", "e2d-sensor-reading-shadow");
      enter.append("text").attr("class", "e2d-sensor-reading");
      enter.append("text").attr("class", "e2d-sensor-label-shadow");
      enter.append("text").attr("class", "e2d-sensor-label");

      // Looks strange, but it propagates data from parent to labels.
      // .selectAll() doesn't do it. We can do it here, before rendering.
      // If labels don't exist yet, enter will propagate data. If they
      // exist, data binding will be updated.
      update.select(".e2d-sensor-label");
      update.select(".e2d-sensor-label-shadow");
      update.select(".e2d-sensor-reading");
      update.select(".e2d-sensor-reading-shadow");
    }

    function renderThermometers(data) {
      var update = g.selectAll(".e2d-sensor.thermometer").data(data.filter(function (d) {
            return d.type === "thermometer";
          })),
          enter = update.enter().append("g")
              .attr("class", "e2d-sensor sensor thermometer")
              .call(dragBehavior);
      supportLabels(enter, update);

      // Note that background and fill are inverted (background covers
      // fill). It's easier to change only height of background instead of
      // manipulating both Y coordinate and height of fill.
      enter.append("rect").attr("class", "e2d-thermometer-fill");
      enter.append("rect").attr("class", "e2d-thermometer-background");
      enter.call(measuringPoint);

      update.attr("transform", transform);
      update.select(".e2d-thermometer-fill")
        .attr("x", em(-0.5 * TH_W))
        .attr("y", em(-0.5 * TH_H))
        .attr("width", em(TH_W))
        .attr("height", em(TH_H));
      thermBg = update.select(".e2d-thermometer-background")
        .attr("x", em(-0.5 * TH_W))
        .attr("y", em(-0.5 * TH_H))
        .attr("width", em(TH_W))
        .attr("height", bgHeight);
      thermReading = update.selectAll(".e2d-sensor-reading, .e2d-sensor-reading-shadow")
        .text(readingText)
        .attr("y", em(-0.5 * TH_H))
        .attr("dy", "-.2em")
        .attr("dx", "-.7em");
      update.selectAll(".e2d-sensor-label, .e2d-sensor-label-shadow")
        .text(labelText)
        .attr("dx", labelDx)
        .attr("dy", "1em")
        .attr("y", em(0.5 * TH_H));

      update.exit().remove();
    }

    function renderAnemometer(data) {
      var update = g.selectAll(".e2d-sensor.anemometer").data(data.filter(function (d) {
            return d.type === "anemometer";
          })),
          enter = update.enter().append("g")
              .attr("class", "e2d-sensor sensor anemometer")
              .call(dragBehavior);
      supportLabels(enter, update);

      enter = enter
        .append("svg")
          .attr("class", "e2d-anemometer-shape")
          .attr("viewBox", "-50 -50 100 100")
          .attr("x", "-3%")
          .attr("y", "-3%")
          .attr("width", "6%")
          .attr("height", "6%")
        .append("g")
          .attr("class", "e2d-anemometer-rot");

      enter.append("path")
          .attr("d", "M-10,10 L0,50 L10,10 Z")
          .attr("transform", "rotate(0)");
      enter.append("path")
          .attr("d", "M-10,10 L0,50 L10,10 Z")
          .attr("transform", "rotate(120)");
      enter.append("path")
          .attr("d", "M-10,10 L0,50 L10,10 Z")
          .attr("transform", "rotate(240)");
      enter.append("circle")
          .attr("r", 12);

      update.attr("transform", transform);
      anemoRot = update.select(".e2d-anemometer-rot")
          .attr("transform", anemometerRotation);
      update.selectAll(".e2d-sensor-label, .e2d-sensor-label-shadow")
        .text(labelText)
        .attr("dx", labelDx)
        .attr("dy", "0.7em")
        .attr("y", "3%");

      update.exit().remove();
    }

    function renderHeatFluxSensors(data) {
      var update = g.selectAll(".e2d-sensor.heatFlux").data(data.filter(function (d) {
            return d.type === "heatFlux";
          })),
          enter = update.enter().append("g")
              .attr("class", "e2d-sensor sensor heatFlux")
              .call(dragBehavior);

      enter = enter.append("g")
          .attr("transform", heatFluxRot);
      enter.append("rect")
          .attr("class", "e2d-heatflux-shape")
          .attr("x", "-3%")
          .attr("y", "-1%")
          .attr("width", "6%")
          .attr("height", "2%");
      enter.append("svg")
          .attr("viewBox", "0 0 6 1")
          .attr("x", "-3%")
          .attr("y", "-1%")
          .attr("width", "6%")
          .attr("height", "2%")
        .append("path")
          .attr("class", "e2d-heatflux-pattern")
          .attr("d", "M0,0L1,1L2,0L3,1L4,0L5,1L6,0");
      enter.call(measuringPoint);

      supportLabels(enter, update);

      update.attr("transform", transform);
      heatFluxReading = update.selectAll(".e2d-sensor-reading, .e2d-sensor-reading-shadow")
        .text(heatFluxReadingText)
        .attr("y", "-1%")
        .attr("dy", "-.2em")
        .attr("dx", "-1.8em");
      update.selectAll(".e2d-sensor-label, .e2d-sensor-label-shadow")
        .text(labelText)
        .attr("dx", labelDx)
        .attr("dy", "0.9em")
        .attr("y", "1%");

      update.exit().remove();
    }

    // Public API.
    api = {
      update: function () {
        thermBg.attr("height", bgHeight);
        thermReading.text(readingText);
        anemoRot.attr("transform", anemometerRotation);
        heatFluxReading.text(heatFluxReadingText);
      },

      renderSensors: function () {
        if (!sensors) return;

        renderThermometers(sensors);
        renderAnemometer(sensors);
        renderHeatFluxSensors(sensors);
      },

      bindSensorsArray: function (newSensors) {
        sensors = newSensors;
      },

      setMinMaxTemp: function (min, max) {
        thermValScale.domain([min, max]);
      }
    };

    return api;
  };
});

/*global define: false, $: false */

define('models/energy2d/views/renderer',['require','models/energy2d/views/heatmap','models/energy2d/views/heatmap-webgl','models/energy2d/views/webgl-status','models/energy2d/views/vectormap','models/energy2d/views/vectormap-webgl','models/energy2d/views/photons','models/energy2d/views/parts','models/energy2d/views/sensors'],function(require) {
  var HeatmapView        = require('models/energy2d/views/heatmap'),
      HeatmapWebGLView   = require('models/energy2d/views/heatmap-webgl'),
      WebGLStatusView    = require('models/energy2d/views/webgl-status'),
      VectormapView      = require('models/energy2d/views/vectormap'),
      VectormapWebGLView = require('models/energy2d/views/vectormap-webgl'),
      PhotonsView        = require('models/energy2d/views/photons'),
      PartsView          = require('models/energy2d/views/parts'),
      SensorsView        = require('models/energy2d/views/sensors');


  return function Renderer(SVGContainer, model) {
    var api,

        heatmap_view,
        velocity_view,
        photons_view,
        parts_view,
        sensors_view,
        webgl_status = new WebGLStatusView(null, model),
        $status = webgl_status.getHTMLElement(),
        $canvasCont = $("<div id='e2d-canvas-views'>"),
        canvasCount = 0,

        isSetup = false;

    function setAsNextLayer(view) {
      var $layer = view.getHTMLElement();

      $layer.css('width', '100%');
      $layer.css('height', '100%');
      $layer.css('position', 'absolute');
      $layer.css('top', 0);
      $layer.css('left', 0);
      $layer.css('z-index', canvasCount);
      canvasCount += 1;

      $canvasCont.append($layer);

      // Note that we SHOULD implement it in the following way:
      //
      // var $layer = view.getHTMLElement(),
      //     fo = g.append("foreignObject").attr({
      //       width: "100%",
      //       height: "100%"
      //     }).style({
      //       width: "100%",
      //       height: "100%"
      //     });
      // $layer.css('width', '100%');
      // if (!customHeight) $layer.css('height', '100%');
      // $layer.appendTo(fo);
      //
      // but foreignObject support is completely broken in Chrome (works fine in Firefox).
      // TODO: check if new version (30+?) fixes that.
    }

    function setupCanvasViews() {
      var props = model.properties;

      $canvasCont.empty();
      canvasCount = 0;
      // Use isWebGLActive() method, not use_WebGL property. The fact that
      // use_WebGL option is set to true doesn't mean that WebGL can be
      // initialized. It's only a preference.
      if (model.isWebGLActive()) {
        heatmap_view = new HeatmapWebGLView();
        velocity_view = new VectormapWebGLView();
        // Both VectormapWebGL and HeatmapWebGL use common canvas,
        // so it's enough to set it only once as the next layer.
        setAsNextLayer(velocity_view);
      } else {
        heatmap_view = new HeatmapView();
        setAsNextLayer(heatmap_view);
        velocity_view = new VectormapView();
        setAsNextLayer(velocity_view);
      }
      photons_view = new PhotonsView();
      setAsNextLayer(photons_view);

      // It must be called after attaching to parent node.
      heatmap_view.resize();
      velocity_view.resize();
      photons_view.resize();

      // Bind models to freshly created views.
      if (model.isWebGLActive()) {
        heatmap_view.bindHeatmapTexture(model.getTemperatureTexture());
        velocity_view.bindVectormapTexture(model.getVelocityTexture(), props.grid_width, props.grid_height, 25);
      } else {
        heatmap_view.bindHeatmap(model.getTemperatureArray(), props.grid_width, props.grid_height);
        velocity_view.bindVectormap(model.getUVelocityArray(), model.getVVelocityArray(), props.grid_width, props.grid_height, 25);
      }
      photons_view.bindPhotonsArray(model.getPhotonsArray(), props.model_width, props.model_height);
    }

    function setVisOptions () {
      var props = model.properties;
      velocity_view.enabled = props.velocity;
      heatmap_view.setMinTemperature(props.minimum_temperature);
      heatmap_view.setMaxTemperature(props.maximum_temperature);
      heatmap_view.setColorPalette(props.color_palette_type);
      sensors_view.setMinMaxTemp(props.minimum_temperature, props.maximum_temperature);
    }

    api = {
      getHeightForWidth: function(width) {
        return width * model.properties.grid_height / model.properties.grid_width;
      },

      setup: function (model) {
        isSetup = true;
        setupCanvasViews();

        parts_view.bindPartsArray(model.getPartsArray());
        sensors_view.bindSensorsArray(model.getSensorsArray());
        webgl_status.bindModel(model);
        setVisOptions();

        parts_view.renderParts();
        sensors_view.renderSensors();
        webgl_status.render();
        api.update();

        model.addPropertiesListener("use_WebGL", function() {
          setupCanvasViews();
          setVisOptions();
          webgl_status.render();
          api.update();
        });
        model.addPropertiesListener(["color_palette_type", "velocity",
                                     "minimum_temperature", "maximum_temperature"], function () {
          setVisOptions();
          api.update();
        });
        model.on('tick.view-update', api.update);
        model.on('partsChanged.view-update', function () {
          parts_view.renderParts();
        });
        model.on('sensorsChanged.view-update', function () {
          sensors_view.renderSensors();
        });
      },

      update: function () {
        if (!isSetup) return;
        heatmap_view.renderHeatmap();
        velocity_view.renderVectormap();
        photons_view.renderPhotons();
        sensors_view.update();
      },

      resize: function () {
        // Ignore all resize() callbacks if view isn't already set up.
        if (!isSetup) return;
        heatmap_view.resize();
        velocity_view.resize();
        photons_view.resize();
        parts_view.renderParts();
        sensors_view.renderSensors();
        api.update();
      },

      reset: function () {},

      setFocus: function () {
        if (model.get("enableKeyboardHandlers")) {
          this.$el.focus();
        }
      },

      bindModel: function(newModel) {
        model = newModel;
      }
    };

    (function() {
      // Instantiate SVG views.
      var viewport = SVGContainer.appendViewport();

      parts_view = new PartsView(SVGContainer, viewport.append("g"));
      sensors_view = new SensorsView(SVGContainer, viewport.append("g"));
      SVGContainer.$el.append($canvasCont);
      SVGContainer.$el.append($status);
    }());

    return api;
  };
});
/*global define: false */

define('models/energy2d/views/view',['require','common/views/svg-container','models/energy2d/views/renderer'],function (require) {
  var SVGContainer = require('common/views/svg-container'),
      Renderer     = require('models/energy2d/views/renderer');

  return function (model, modelUrl) {
    return new SVGContainer(model, modelUrl, Renderer, {origin: 'top-left'});
  };

});

/*global define: false, d3: false */

define('models/energy2d/benchmarks/benchmarks',['common/performance'],function () {

  var performance = require("common/performance"),

      TEST_TIME = 5000,
      WARMUP_TIME = 1000;

  return function Benchmarks(controller) {
    var start;

    var benchmarks = [
      //
      // WebGL OFF
      //
      {
        name: "engine (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          // warmup
          controller.model.start();
          setTimeout(function() {
            controller.model.stop();

            controller.model.properties.use_WebGL = false;
            performance.collectData(true);
            start = controller.model.get("time");

            setTimeout(function() {
              // actual fps calculation
              controller.model.start();
              setTimeout(function() {
                controller.model.stop();

                performance.collectData(false);
                done(performance.getAvgTime("engine"));

              }, TEST_TIME);
            }, 100);
          }, WARMUP_TIME);
        }
      },
      {
        name: "JS rendering (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(performance.getAvgTime("js-rendering"));
        }
      },
      {
        name: "tick (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(performance.getAvgTime("tick"));
        }
      },
      {
        name: "gap b/w frames (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(performance.getAvgTime("gap"));
        }
      },
      {
        name: "fps",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          var elapsedModelTime = controller.model.get('time') - start;
          done(elapsedModelTime / (controller.model.get('timeStepsPerTick') * controller.model.get('timeStep')) * 1000 / TEST_TIME);
        }
      },
      //
      // WebGL ON
      //
      {
        name: "engine WebGL (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          // warmup
          controller.model.start();
          setTimeout(function() {
            controller.model.stop();

            controller.model.properties.use_WebGL = true;
            performance.collectData(true);
            start = controller.model.get("time");

            setTimeout(function() {
              // actual fps calculation
              controller.model.start();
              setTimeout(function() {
                controller.model.stop();

                performance.collectData(false);
                done(performance.getAvgTime("engine"));

              }, TEST_TIME);
            }, 100);
          }, WARMUP_TIME);
        }
      },
      {
        name: "JS rendering WebGL (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(performance.getAvgTime("js-rendering"));
        }
      },
      {
        name: "tick WebGL (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(performance.getAvgTime("tick"));
        }
      },
      {
        name: "gap b/w frames WebGL (ms)",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          done(performance.getAvgTime("gap"));
        }
      },
      {
        name: "fps WebGL",
        numeric: true,
        formatter: d3.format("5.1f"),
        run: function(done) {
          var elapsedModelTime = controller.model.get('time') - start;
          done(elapsedModelTime / (controller.model.get('timeStepsPerTick') * controller.model.get('timeStep')) * 1000 / TEST_TIME);
        }
      }
    ];

    return benchmarks;

  };
});

/*global define: false */

define('models/energy2d/controllers/scripting-api',[],function () {

  return function Energy2DScriptingAPI (parent) {

    return {
      getTemperatureAt: function getTemperatureAt(x, y) {
        return parent.model.getTemperatureAt(x, y);
      },

      getAverageTemperatureAt: function getAverageTemperatureAt(x, y) {
        return parent.model.getAverageTemperatureAt(x, y);
      },

      getSensor: function getSensor(i) {
        return parent.model.getSensorsArray()[i];
      },

      getPart: function getPart(i) {
        return parent.model.getPartsArray()[i];
      },

      addPart: function addPart(props) {
        parent.model.addPart(props);
      },

      removePart: function removePart(i) {
        parent.model.removePart(i);
      },

      getNumberOfParts: function getNumberOfParts() {
        return parent.model.getPartsArray().length;
      },

      syncTemperature: function syncTemperature() {
        parent.model.syncTemperature();
      }
    };
  };
});

/*global
  define
*/
/*jslint onevar: true*/
define('models/energy2d/controllers/controller',['require','common/controllers/model-controller','models/energy2d/modeler','models/energy2d/views/view','models/energy2d/benchmarks/benchmarks','models/energy2d/controllers/scripting-api'],function (require) {
  // Dependencies.
  var ModelController   = require("common/controllers/model-controller"),
      Model             = require('models/energy2d/modeler'),
      ModelContainer    = require('models/energy2d/views/view'),
      Benchmarks        = require('models/energy2d/benchmarks/benchmarks'),
      ScriptingAPI      = require('models/energy2d/controllers/scripting-api');

  return function (modelUrl, modelOptions, interactiveController) {
    return new ModelController(modelUrl, modelOptions, interactiveController,
                               Model, ModelContainer, ScriptingAPI, Benchmarks);
  };
});


/*global define, $*/

define('common/models/dataset',[],function () {
  var datasetCount = 0;

  return function Dataset(definition, model) {
        // Public API.
    var dataset,
        rowIndex,
        tableData,
        xProperty,
        properties,
        namespace = "dataset" + (++datasetCount);

    function initialize() {
      rowIndex = 0;
      tableData = [];
      registerModelListeners();
      xProperty = definition.timeSeries.xProperty;
      properties = definition.timeSeries.properties;
      appendPropertyRow();
    }

    function appendPropertyRow() {
      var i, rowData = [];
      rowIndex++;
      rowData.push(model.get(xProperty));
      for(i = 0; i < properties.length; i++) {
        rowData.push(model.get(properties[i]));
      }
      tableData.push(rowData);
    }

    function registerModelListeners() {
      // Namespace listeners to '.dataset'
      model.on('tick.'+namespace, function () {
        appendPropertyRow();
      });
    }

    // Public API.
    dataset = {

      getData: function(propArray) {
        function copy(array) {
          var ret = [];
          array.forEach(function(element) {
            ret.push(element);
          });
          return ret;
        }
        var i, row, index, j, result = [], rowResult;
        if (propArray === undefined) {
          return copy(tableData);
        } else {
          for(i = 0; i < tableData.length; i++) {
            row = tableData[i];
            rowResult = [];
            for(j = 0; j < propArray.length; j++) {
              index = definition.properties.indexOf(propArray[j]);
              rowResult.push(row[index]);
            }
            result.push(rowResult);
          }
          return [result];
        }
      },

      reset: function () {
        tableData = $.extend(true, [], definition.tableData);
        rowIndex = 0;
      },

      /**
        Used when manually adding a row of property values to the table.
      */
      appendPropertyRow: appendPropertyRow,

      // Returns serialized component definition.
      serialize: function () {
        // start with the initial dataset definition.
        var result = $.extend(true, {}, definition);
        // add tableData
        result.tableData = tableData;
        return result;
      }
    };

    initialize();

    // Return Public API object.
    return dataset;
  };
});

/*global define, $*/

define('common/controllers/experiment-controller',['require','common/controllers/interactive-metadata','common/validator','common/models/dataset'],function (require) {
  var metadata  = require('common/controllers/interactive-metadata'),
      validator = require('common/validator'),
      Dataset   = require('common/models/dataset');

  return function ExperimentController(experimentDefinition, interactivesController) {
        // Public API.
    var controller,
        model,
        scriptingAPI,
        timeSeriesDatasets,
        currentDataset,
        timeSeries,
        inputs,
        outputs,
        destinations,
        stateButtons,
        onLoadFunc,
        // the state transition button components
        startRun,
        stopRun,
        saveRun,
        nextRun,
        clearAll,
        // arrays of components (graphs or tables) that data are sent to ...
        timeSeriesDestinations,
        parameterSeriesDestinations,
        timeSeriesGraph;

    function initialize() {
      // Validate component definition, use validated copy of the properties.
      experimentDefinition = validator.validateCompleteness(metadata.experiment, experimentDefinition);
      timeSeries    = experimentDefinition.timeSeries;
      inputs        = experimentDefinition.parameters.inputs;
      outputs       = experimentDefinition.parameters.outputs;
      destinations  = experimentDefinition.destinations;
      stateButtons  = experimentDefinition.stateButtons;
      timeSeriesDatasets = [];

      interactivesController.on("modelLoaded.experimentController", function () {
        scriptingAPI = interactivesController.getScriptingAPI();
        model = interactivesController.getModel();
        setupModelParameters();
      });

      setup();
    }

    function setupModelParameters() {
      model.defineParameter('experimentCleared', { initialValue: false }, function () {
        if (model.get('experimentCleared')) {
          goToNextRun();
          model.set('experimentCleared', false);
        }
      });
      model.defineParameter('experimentRunning', { initialValue: false }, function () {
        if (model.get('experimentRunning')) {
          goToRunStarted();
        } else {
          goToRunStopped();
        }
      });
    }

    function setup() {

      // private functions ...
      function setupStateButtons() {
        startRun = interactivesController.getComponent(stateButtons.startRun);
        stopRun  = interactivesController.getComponent(stateButtons.stopRun);
        saveRun  = interactivesController.getComponent(stateButtons.saveRun);
        nextRun  = interactivesController.getComponent(stateButtons.nextRun);
        clearAll = interactivesController.getComponent(stateButtons.clearAll);
      }

      function setupDestinationComponents() {
        var i, j, destination, component;
        timeSeriesDestinations = [];
        parameterSeriesDestinations = [];
        for (i = 0; i < destinations.length; i++) {
          destination = destinations[i];
          destination.components = [];
          for (j = 0; j < destination.componentIds.length; j++) {
            destination.components.push(interactivesController.getComponent(destination.componentIds[j]));
          }
          switch (destination.type) {
          case "timeSeries":
            timeSeriesDestinations.push(destination);
            break;
          case "parameterSeries":
            parameterSeriesDestinations.push(destination);
            break;
          default:
            throw new Error("Experiment destination: invalid destination type: " + destination.type);
          }
        }
        timeSeriesGraph = undefined;
        for (i = 0; i < timeSeriesDestinations.length; i++) {
          destination = timeSeriesDestinations[i];
          for (j = 0; j < destination.components.length; j++) {
            component = destination.components[j];
            if (component.type !== undefined && component.type === "graph") {
              timeSeriesGraph = component;
              return;
            }
          }
        }
      }

      function setupStateButtonActions() {
        startRun.setAction("set('experimentRunning', true);");
        stopRun.setAction("set('experimentRunning', false);");
        saveRun.setAction(function () {
          var i, j, parameterSeriesDestination;
          timeSeriesDatasets.push(currentDataset);
          for (i = 0; i < parameterSeriesDestinations.length; i++) {
            parameterSeriesDestination = parameterSeriesDestinations[i];
            for (j = 0; j < parameterSeriesDestination.components.length; j++) {
              parameterSeriesDestination.components[j].appendDataPropertiesToComponent();
            }
          }
          saveRun.setDisabled(true);
        });
        nextRun.setAction("set('experimentCleared', true);");
        clearAll.setAction(function () {
          interactivesController.reloadInteractive();
        });
      }

      // setup experiment ...
      setupStateButtons();
      setupDestinationComponents();
      setupStateButtonActions();
    }

    function addOlderRunsToGraph() {
      var i;
      if (timeSeriesGraph) {
        for (i = 0; i < timeSeriesDatasets.length; i++) {
          timeSeriesGraph.addDataSet(timeSeriesDatasets[i].getData());
        }
        timeSeriesGraph.update();
      }
    }

    function unfreezeInputParameters() {
      for (var i = 0; i < inputs.length; i++) {
        model.unfreeze(inputs[i]);
      }
    }


    function freezeInputParameters() {
      for (var i = 0; i < inputs.length; i++) {
        model.freeze(inputs[i]);
      }
    }

    function goToRunStarted() {
      startRun.setDisabled(true);
      stopRun.setDisabled(false);
      saveRun.setDisabled(true);
      nextRun.setDisabled(true);
      clearAll.setDisabled(false);
      freezeInputParameters();
      currentDataset = new Dataset({ timeSeries: timeSeries }, model);
      scriptingAPI.api.start();
    }

    function goToRunStopped() {
      startRun.setDisabled(true);
      stopRun.setDisabled(true);
      saveRun.setDisabled(false);
      nextRun.setDisabled(false);
      scriptingAPI.api.stop();
    }

    function goToNextRun() {
      startRun.setDisabled(false);
      stopRun.setDisabled(true);
      saveRun.setDisabled(true);
      nextRun.setDisabled(true);
      model.set('experimentCleared', false);
      interactivesController.reloadModel({propertiesToRetain: inputs, cause: "new-run"});
      unfreezeInputParameters();
      addOlderRunsToGraph();
    }

    // Public API.
    controller = {

      setOnLoadScript: function(onLoadScript) {
        onLoadFunc = onLoadScript;
      },

      // Returns serialized component definition.
      serialize: function () {
        // start with the initial experimentDefinition.
        var result = $.extend(true, {}, experimentDefinition);
        // possibly add saved data
        return result;
      }
    };

    initialize();

    // Return Public API object.
    return controller;
  };
});

/*global define, $, setTimeout, document, window */

define('common/controllers/interactives-controller',['require','seedrandom','lab.config','arrays','fastclick','underscore','common/alert','common/validator','common/i18n','common/controllers/interactive-metadata','common/controllers/language-select','common/controllers/data-set','common/controllers/bar-graph-controller','common/controllers/graph-controller','common/controllers/export-controller','common/controllers/log-controller','common/controllers/scripting-api','common/controllers/button-controller','common/controllers/checkbox-controller','common/controllers/text-controller','common/controllers/image-controller','common/controllers/radio-controller','common/controllers/slider-controller','common/controllers/pulldown-controller','common/controllers/joystick-controller','common/controllers/color-indicator-controller','common/controllers/spectrometer-controller','common/controllers/numeric-output-controller','common/controllers/table-controller','common/controllers/parent-message-api','common/controllers/thermometer-controller','common/controllers/playback-controller','common/controllers/div-controller','common/dispatch-support','common/controllers/help-system','common/views/tooltip','common/cookies','common/controllers/setup-banner','common/controllers/about-dialog','common/controllers/share-dialog','common/controllers/credits-dialog','common/layout/semantic-layout','common/layout/templates','models/md2d/controllers/controller','models/signal-generator/controller','models/iframe/controller','models/sensor/controller','models/dual-sensor/controller','models/dual-sensor-connector/controller','models/sensor-connector/controller','models/labquest2/controller','models/energy2d/controllers/controller','common/controllers/experiment-controller'],function (require) {
  // Dependencies.
                                require('seedrandom');
  var labConfig               = require('lab.config'),
      arrays                  = require('arrays'),
      FastClick               = require('fastclick'),
      _                       = require('underscore'),
      alert                   = require('common/alert'),
      validator               = require('common/validator'),
      getI18n                 = require('common/i18n'),
      metadata                = require('common/controllers/interactive-metadata'),
      languageSelect          = require('common/controllers/language-select'),
      DataSet                 = require('common/controllers/data-set'),
      BarGraphController      = require('common/controllers/bar-graph-controller'),
      GraphController         = require('common/controllers/graph-controller'),
      ExportController        = require('common/controllers/export-controller'),
      LogController           = require('common/controllers/log-controller'),
      ScriptingAPI            = require('common/controllers/scripting-api'),
      ButtonController        = require('common/controllers/button-controller'),
      CheckboxController      = require('common/controllers/checkbox-controller'),
      TextController          = require('common/controllers/text-controller'),
      ImageController         = require('common/controllers/image-controller'),
      RadioController         = require('common/controllers/radio-controller'),
      SliderController        = require('common/controllers/slider-controller'),
      PulldownController      = require('common/controllers/pulldown-controller'),
      JoystickController      = require('common/controllers/joystick-controller'),
      ColorIndicatorController = require('common/controllers/color-indicator-controller'),
      SpectrometerController  = require('common/controllers/spectrometer-controller'),
      NumericOutputController = require('common/controllers/numeric-output-controller'),
      TableController         = require('common/controllers/table-controller'),
      ParentMessageAPI        = require('common/controllers/parent-message-api'),
      ThermometerController   = require('common/controllers/thermometer-controller'),
      PlaybackController      = require('common/controllers/playback-controller'),
      DivController           = require('common/controllers/div-controller'),
      DispatchSupport         = require('common/dispatch-support'),
      HelpSystem              = require('common/controllers/help-system'),
      tooltip                 = require('common/views/tooltip'),
      cookies                 = require('common/cookies'),

      // Helper function which just provides banner definition.
      setupBanner             = require('common/controllers/setup-banner'),
      AboutDialog             = require('common/controllers/about-dialog'),
      ShareDialog             = require('common/controllers/share-dialog'),
      CreditsDialog           = require('common/controllers/credits-dialog'),
      SemanticLayout          = require('common/layout/semantic-layout'),
      templates               = require('common/layout/templates'),

      ModelControllerFor = {
        'md2d':             require('models/md2d/controllers/controller'),
        'signal-generator': require('models/signal-generator/controller'),
        'iframe-model':     require('models/iframe/controller'),
        'sensor':           require('models/sensor/controller'),
        'dual-sensor':      require('models/dual-sensor/controller'),
        'dual-sensor-connector': require('models/dual-sensor-connector/controller'),
        'sensor-connector': require('models/sensor-connector/controller'),
        'labquest2':        require('models/labquest2/controller'),
        'energy2d':         require('models/energy2d/controllers/controller')
      },

      ExperimentController = require('common/controllers/experiment-controller'),

      // Set of available components.
      // - Key defines 'type', which is used in the interactive JSON.
      // - Value is a constructor function of the given component.
      // Each constructor should assume that it will be called with
      // following arguments:
      // 1. component definition (unmodified object from the interactive JSON),
      // 2. scripting API object,
      // 3. public API of the InteractiveController.
      // Of course, some of them can be passed unnecessarily, but
      // the InteractiveController follows this convention.
      //
      // The instantiated component should provide following interface:
      // # serialize()           - function returning a JSON object, which represents current state
      //                           of the component. When component doesn't change its state,
      //                           it should just return a copy (!) of the initial component definition.
      // # getViewContainer()    - function returning a jQuery object containing
      //                           DOM elements of the component.
      // # modelLoadedCallback(model) - optional function with , a callback which is called when the model is loaded.
      // # resize()              - optional function taking no arguments, a callback
      //                           which is called by the layout algorithm when component's container
      //                           dimensions are changed. This lets component to adjust itself to the
      //                           new container dimensions.
      //
      // Note that each components view container (so, jQuery object returned by getViewContainer() has to
      // have class 'component'! It's required and checked in the runtime by the interactive controller.
      // It ensures good practices while implementing new components.
      // Please see: src/sass/lab/_interactive-component.sass to check what this CSS class defines.
      ComponentConstructor = {
        'text':          TextController,
        'image':         ImageController,
        'button':        ButtonController,
        'checkbox':      CheckboxController,
        'pulldown':      PulldownController,
        'radio':         RadioController,
        'thermometer':   ThermometerController,
        'barGraph':      BarGraphController,
        'graph':         GraphController,
        'slider':        SliderController,
        'colorIndicator': ColorIndicatorController,
        'joystick':      JoystickController,
        'spectrometer':  SpectrometerController,
        'numericOutput': NumericOutputController,
        'table':         TableController,
        'div':           DivController,
        'playback':      PlaybackController
      };

  function clone(obj) {
    var copy;
    // Handle the 3 simple types, and null or undefined.
    if (null == obj || "object" !== typeof obj) return obj;
    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }
    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj! Its type isn't supported.");
  }

  return function InteractivesController(interactiveReference, viewSelector) {

    var interactive = {},
        controller = {},
        initialInteractiveConfig,
        initialModelConfig,
        experimentController,
        experimentDefinition,
        modelController,
        model,
        currentModelID,
        $interactiveContainer,
        $fastClickContainer,
        helpSystem,
        i18n,
        modelDefinitions = [],
        modelHash = {},
        componentModelLoadedCallbacks,
        dataSetModelLoadedCallbacks,
        willResetModelCallbacks = [],
        ignoreModelResetEvent = false,
        initialModelLoad,

        // Hash of instantiated components.
        // Key   - component ID.
        // Value - array of component instances.
        componentByID = {},

        // Simple list of instantiated components.
        componentList = [],

        // List of properties that are bound to components and should be retained during
        // model reload / reset.
        propertiesRetainedByComponents = [],

        // Hash containing all public (see .addDataSet() method) data sets.
        publicDataSetsByName,

        // API for scripts defined in the interactive JSON file.
        // and additional model-specific scripting api if one is defined
        scriptingAPI,

        // Handles exporting data to DataGames, if 'exports' are specified.
        exportController,

        // Handles logging events to LARA or any parent that implements LARA-logging interface.
        logController,

        // Doesn't currently have any public methods, but probably will.
        parentMessageAPI,

        // Dialogs which can be shown using banner.
        aboutDialog,
        shareDialog,
        creditsDialog,

        semanticLayout,
        getNextTabIndex,
        randSeed,

        dispatch = new DispatchSupport("modelLoaded", "interactiveRendered", "modelReset", "resize",
                                       "interactiveRequested", "interactiveWillReload");

    // simple tabindex support, also exposed via api.getNextTabIndex()
    getNextTabIndex = function () {
      var tabIndex = -1;
      return function() {
        return tabIndex++;
      };
    };

    // Use seedrandom library (see vendor/seedrandom) that substitutes an explicitly seeded
    // RC4-based algorithm for Math.random().
    function generateRandomSeed() {
      randSeed = interactive.randomSeed;
      if (randSeed === undefined) {
        // Generate random seed.
        // First ensure that random is random for sure (seedrandom with explicit value could be
        // called during previous interactive load, before model tick etc).
        Math.seedrandom();
        randSeed = Math.random().toString();
      }
      Math.seedrandom(randSeed);
    }

    function getModelDefinition(id) {
      if (modelHash[id]) {
        return modelHash[id];
      }
      throw new Error("No model found with id " + id);
    }

    function layoutInteractive() {
      // Do nothing if this is called before the model loads (for example via a resize event).
      // The interactive will be laid out as soon as the model loads anyway, and furthermore the
      // semantic layout mechanism calls at least one modelController method.
      if (!semanticLayout.isReady()) {
        return;
      }
      semanticLayout.layoutInteractive();
    }

    // ------------------------------------------------------------
    //
    // Handle keyboard shortcuts for model operation.
    //
    // ------------------------------------------------------------

    function setupModelPlayerKeyboardHandler() {
      // Deregister previous keydown handlers. Use namespaces so the code
      // will not inadvertently remove event handlers attached by other code.
      $interactiveContainer.off('keydown.interactiveController');
      if (modelController && modelController.enableKeyboardHandlers()) {
        $interactiveContainer.on('keydown.interactiveController', function(event) {
          var keycode = event.keycode || event.which;
          switch(keycode) {
            case 13:                 // return
            event.preventDefault();
            scriptingAPI.api.start();
            break;

            case 32:                 // space
            event.preventDefault();
            if (!scriptingAPI.api.isStopped()) {
              scriptingAPI.api.stop();
            } else {
              scriptingAPI.api.start();
            }
            break;

            case 37:                 // left-arrow
            event.preventDefault();
            if (!scriptingAPI.api.isStopped()) {
              scriptingAPI.api.stop();
            } else {
              scriptingAPI.api.stepBack();
            }
            break;

            case 39:                 // right-arrow
            event.preventDefault();
            if (!scriptingAPI.api.isStopped()) {
              scriptingAPI.api.stop();
            } else {
              scriptingAPI.api.stepForward();
            }
            break;
          }
        });
        // $interactiveContainer.focus();
      }
    }

    function setupLayout() {
      var template, layout, comp, components, banner, resizeAfterFullscreen;

      if (typeof interactive.template === "string") {
        template = templates[interactive.template];
      } else {
        template = interactive.template;
      }

      // The authored definition of which components go in which container.
      layout = interactive.layout;

      // Banner hash containing components, layout containers and layout definition
      // (components location). Keep it in a separate structure, because we do not
      // expect these objects to be serialized!
      banner = setupBanner(controller, interactive, creditsDialog, aboutDialog, shareDialog);
      // Register callbacks of banner components.
      components = banner.components;
      for (comp in components) {
        if (components.hasOwnProperty(comp)) {
          comp = components[comp];
          if (comp.modelLoadedCallback) {
            // $.proxy ensures that callback will be always executed
            // in the context of correct object ('this' binding).
            componentModelLoadedCallbacks.push($.proxy(comp.modelLoadedCallback, comp));
          }
        }
      }
      // Note that all of these operations create a new object.
      // So interactive definition specified by the author won't be affected.
      // This is important for serialization correctness.
      template = banner.template.concat(template);
      template.forEach(function (container, idx) {
        template[idx] = $.extend({}, container);
      });
      layout = $.extend({}, layout, banner.layout);
      components = $.extend({}, componentByID, banner.components);

      // Setup layout using both author components and components
      // created automatically in this controller.
      semanticLayout.initialize({
        $mainContainer: $interactiveContainer,
        $containersParent: $fastClickContainer,
        containers: template,
        layout: layout,
        components: components,
        aspectRatio: interactive.aspectRatio,
        fontScale: interactive.fontScale,
        padding: interactive.padding
      });

      // We are rendering in embeddable mode if only element on page
      // so resize when window resizes.
      if (onlyElementOnPage()) {
        $(window).off("resize.lab-resize-handler");
        $(window).on("resize.lab-resize-handler", function() {
          controller.resize();
        });
      }

      // in all cases, call resize when entering and existing fullscreen
      resizeAfterFullscreen = function() {
        // need to call twice, as safari requires two attempts before it has
        // the correct dimensions.
        controller.resize();
        setTimeout(controller.resize, 50);
      };
      $(document).off(".lab-fullscreen-change");
      $(document).on("fullscreenchange.lab-fullscreen-change", resizeAfterFullscreen)
                 .on("mozfullscreenchange.lab-fullscreen-change", resizeAfterFullscreen)
                 .on("webkitfullscreenchange.lab-fullscreen-change", resizeAfterFullscreen);
    }

    function createComponent(component) {
          // Get type and ID of the requested component from JSON definition.
      var type = component.type,
          id = component.id,
          comp;

      // Use an appropriate constructor function and create a new instance of the given type.
      // Note that we use constant set of parameters for every type:
      // 1. component definition (exact object from interactive JSON),
      // 2. scripting API object,
      // 3. public API of the InteractiveController.
      comp = new ComponentConstructor[type](component, controller);

      if (!comp.getViewContainer().hasClass("component")) {
        throw new Error("Invalid Interactive Component implementation. Each component has to have 'component' class.");
      }

      // Save the new instance.
      componentByID[id] = comp;
      componentList.push(comp);
      if (component.retainProperty && component.property != null) {
        // All properties that are bound to some interactive component should be retained during
        // model reset or reload.
        propertiesRetainedByComponents.push(component.property);
      }

      // Register component modelLoaded callbacks if available.
      // FIXME. These callbacks should be event listeners.
      if (comp.modelLoadedCallback) {
        // $.proxy ensures that callback will be always executed
        // in the context of correct object ('this' binding).
        componentModelLoadedCallbacks.push($.proxy(comp.modelLoadedCallback, comp));
      }
    }

    /**
      Generic function that accepts either a string or an array of strings,
      and returns the complete string
    */
    function getStringFromArray(str) {
      if (typeof str === 'string') {
        return str;
      }
      return str.join('\n');
    }

    /**
      Validates interactive definition.

      Displays meaningful info in case of any errors. Also an exception is being thrown.

      @param interactive
        hash representing the interactive specification
    */
    function validateInteractive(interactive) {
      var i, len, modelDefinitions, modelDefinition, components, errMsg;

      function validateArray(modelName, array) {
        var i, len, errMsg;
        // Support undefined / null values - just return.
        if (!array) return;

        try {
          for (i = 0, len = array.length; i < len; i++) {
            array[i] = validator.validateCompleteness(metadata[modelName], array[i]);
          }
        } catch (e) {
          errMsg = "Incorrect " + modelName +  " definition:\n" + e.message;
          alert(errMsg);
          throw new Error(errMsg);
        }
      }

      // Validate top level interactive properties.
      try {
        interactive = validator.validateCompleteness(metadata.interactive, interactive);
      } catch (e) {
        errMsg = "Incorrect interactive definition:\n" + e.message;
        alert(errMsg);
        throw new Error(errMsg);
      }

      validateArray("model", interactive.models);
      validateArray("parameter", interactive.parameters);
      validateArray("output", interactive.outputs);
      validateArray("dataSet", interactive.dataSets);
      validateArray("filteredOutput", interactive.filteredOutputs);
      validateArray("helpTip", interactive.helpTips);

      // Validate also nested structures.
      modelDefinitions = interactive.models;
      for (i = 0, len = modelDefinitions.length; i < len; i++) {
        modelDefinition = modelDefinitions[i];
        validateArray("parameter", modelDefinition.parameters);
        validateArray("output", modelDefinition.outputs);
        validateArray("filteredOutput", modelDefinition.filteredOutputs);
      }


      // If an experiment template exists validate nested experiment structures.
      if (interactive.experiment) {
        experimentDefinition = interactive.experiment;
        validator.validateCompleteness(metadata.experimentTimeSeries, experimentDefinition.timeSeries);
        validateArray("experimentParameter", experimentDefinition.parameters);
        validateArray("experimentDestination", experimentDefinition.destinations);
        validateArray("experimentSavedRun", experimentDefinition.savedRuns);
      }

      components = interactive.components;
      try {
        for (i = 0, len = components.length; i < len; i++) {
          components[i] = validator.validateCompleteness(metadata[components[i].type], components[i]);
        }
      } catch (e) {
        errMsg = "Incorrect " + components[i].type + " component definition:\n" + e.message;
        alert(errMsg);
        throw new Error(errMsg);
      }

      // Validate exporter, if any...
      if (interactive.exports) {
        try {
          interactive.exports = validator.validateCompleteness(metadata.exports, interactive.exports);
        } catch (e) {
          errMsg = "Incorrect exports definition:\n" + e.message;
          alert(errMsg);
          throw new Error(errMsg);
        }
      }

      if (interactive.logging) {
        try {
          interactive.logging = validator.validateCompleteness(metadata.logging, interactive.logging);
        } catch (e) {
          errMsg = "Incorrect logging definition:\n" + e.message;
          alert(errMsg);
          throw new Error(errMsg);
        }
      }

      return interactive;
    }

    /**
      Is the Interactive the only element on the page?

      An Interactive can either be displayed as the only content on a page
      (often in an iframe) or in a dom element on a page with other elements.

      TODO: make more robust
      This function makes a simplifying assumption that the Interactive is the
      only content on the page if the parent is the <body> element
    */
    function onlyElementOnPage() {
      return $interactiveContainer.parent().prop("nodeName") === "BODY";
    }

    /**
      Setup theme (which is just a CSS class of the main interactive container).
     */
    function setupTheme() {
      var theme = interactive.theme;
      if (arrays.isArray(theme)) {
        // ["a", "b"] => "lab-theme-a lab-theme-b"
        theme = theme.map(function (el) { return 'lab-theme-'+el; }).join(' ');
      } else if (theme) {
        theme = 'lab-theme-' + theme;
      } else {
        theme = '';
      }
      $interactiveContainer.alterClass('lab-theme-*', theme);
    }

    /**
      The main method called when this controller is created.

      Populates the element pointed to by viewSelector with divs to contain the
      molecule container (view) and the various components specified in the interactive
      definition, and

      @param newInteractive
        hash representing the interactive specification
    */
    function loadInteractive(newInteractive) {
      // Cleanup container!
      $interactiveContainer.empty();

      // Attach FastClick only to the .lab-fastclick-container DIV. We don't want to affect rest of the
      // web page (e.g. by attaching FastClick to "body" or window), let its developer decide whether
      // FastClick should be used there or not. It solves two issues on mobile browsers:
      // - eliminates 300ms delay between a physical tap and the firing of a click event
      // - fixes sticky :hover state (https://www.pivotaltracker.com/story/show/58373748)
      //
      // Unfortunatelly we cannot attach FastClick to the whole interactive container, as it breaks
      // e.g. jQuery context menu.
      // See: https://www.pivotaltracker.com/story/show/63386470
      // Components have choice whether to attach themselves to .lab-interactive-container
      // or .lab-fastclick-container.
      $fastClickContainer = $('<div class="lab-fastclick-container">');
      $fastClickContainer.appendTo($interactiveContainer);
      FastClick.attach($fastClickContainer[0]);

      // We have to create a new container for dialogs. When jQuery UI dialog is open, it moves
      // all its sibling elements before itself (kind of z indexing). It means all the siblings
      // are removed from DOM and then added again. Besides performance it can cause unwanted
      // effects in some cases (e.g. iframe content is reloaded). Separate container that contains
      // only dialogs ensures that it won't happen.
      $fastClickContainer.append('<div class="lab-dialog-container">');

      // Each time we load a new interactive, we assume that it would be an "initial" model load.
      // This flag is used to decide whether parameters should be retained or not.
      // During the initial model load we obviously don't want to retain parameters.
      initialModelLoad = true;

      controller.interactive = newInteractive;

      // Save initial interactive config for reload method (so it can be synchronous).
      initialInteractiveConfig = $.extend(true, {}, controller.interactive);
      // Validate interactive.
      controller.interactive = validateInteractive(controller.interactive);
      interactive = controller.interactive;
      // Ensure that interactive initialization is always the same if it's desired
      // ("randomSeed" paramenter is provided).
      generateRandomSeed();
      // Setup CSS class of the main container.
      setupTheme();
      // Set up the list of possible modelDefinitions.
      modelDefinitions = interactive.models;
      for (var i = 0, len = modelDefinitions.length; i < len; i++) {
        modelHash[modelDefinitions[i].id] = modelDefinitions[i];
      }
      // Try to load the first model (in order) and initialize interactive.
      var firstModel = modelDefinitions[0];
      if (firstModel && firstModel.url) {
        // Model has to be downloaded, it's async operation so start with it.
        loadModel(firstModel.id);
        initializeInteractive();
      } else if (firstModel && firstModel.model) {
        // Model is provided inside Interactive JSON, so setup interactive first and then
        // load a model.
        initializeInteractive();
        loadModel(firstModel.id, firstModel.model);
      }
    }

    function cleanupInteractive() {
      // Clear component instances.
      componentList = [];
      componentByID = {};
      // Internal onLoad callbacks (TODO REFACTOR ME)
      dataSetModelLoadedCallbacks = [];
      componentModelLoadedCallbacks = [];
      // And list of properties bound to components.
      propertiesRetainedByComponents = [];
    }

    function initializeInteractive() {
      cleanupInteractive();

      i18n = getI18n(interactive.lang);
      // Setup menu that lets users change language of the interactive. Do it ASAP, as it involves
      // async download of interactive metadata.
      languageSelect('#lang-icon', controller);

      creditsDialog = new CreditsDialog(".lab-dialog-container", i18n, interactive);
      shareDialog = new ShareDialog(".lab-dialog-container", viewSelector, i18n, interactive);
      if (interactive.about || interactive.subtitle) {
        aboutDialog = new AboutDialog(".lab-dialog-container", i18n, interactive);
      } else {
        // Display credits if author hasn't provided any specific information.
        aboutDialog = creditsDialog;
      }

      var modelDef = controller.interactive.models[0];
      modelController = createModelController(modelDef.type, modelDef.modelUrl, null);
      // also be sure to get notified when the underlying model changes
      // (this catches reloads)
      modelController.on('modelReset', modelResetHandler);

      // Create Scripting API
      scriptingAPI = new ScriptingAPI(controller, null);
      // Extend universal Interactive scriptingAPI with optional model-specific scripting API
      if (modelController.ScriptingAPI) {
        scriptingAPI.extend(modelController.ScriptingAPI);
      }
      // Expose API to global namespace (prototyping / testing using the browser console).
      scriptingAPI.exposeScriptingAPI();

      // Setup data sets.
      publicDataSetsByName = {};
      interactive.dataSets.forEach(function (dataSetDefinition) {
        // Data set will automatically register itself in interactive controller (it will call
        // .addDataSet() method).
        new DataSet(dataSetDefinition, controller);
      });

      // Setup help system if help tips are defined.
      if (interactive.helpTips.length > 0) {
        helpSystem = new HelpSystem(interactive.helpTips, $fastClickContainer);
      }

      if (interactive.helpOnLoad || interactive.aboutOnLoad) {
        controller.on("interactiveRendered.helpOnLoad", function () {
          // Make sure that this callback is executed only once.
          controller.on("interactiveRendered.helpOnLoad", null);

          function hashCode(string) {
            var hash = 0, len = string.length, i, c;
            if (len === 0) return hash;
            for (i = 0; i < len; i++) {
              c = string.charCodeAt(i);
              hash = ((hash<<5) - hash) + c;
              hash = hash & hash;
            }
            return hash;
          }
          var hash = hashCode(JSON.stringify(interactive));
          // When `helpOnLoad` is set to true, the help mode will be automatically shown,
          // but only when user opens interactive for the first time.
          if (helpSystem && interactive.helpOnLoad && !cookies.hasItem("lab-help-" + hash)) {
            helpSystem.start();
            cookies.setItem("lab-help-" + hash, true);
          }
          // `aboutOnLoad` follows the same pattern.
          if (interactive.aboutOnLoad && !cookies.hasItem("lab-about-" + hash)) {
            aboutDialog.open();
            cookies.setItem("lab-about-" + hash, true);
          }
        });
      }

      // Replace native tooltips with custom, styled and responsive tooltips.
      tooltip($interactiveContainer);

      // Create interactive components
      var componentJsons = controller.interactive.components || [];

      for (var i = 0, len = componentJsons.length; i < len; i++) {
        createComponent(componentJsons[i]);
      }

      // Setup messaging with CODAP (or other data manager) if present

      // WARNING: due to deficiencies in iframePhone (as of iframePhone 1.1), this must take
      // place in the same event loop as the creation of parentMessageAPI (otherwise, the
      // codap-present message may be swallowed by parentMessageAPI's iframePhone instance, with the
      // result that exportController.canExportData never becomes true)

      exportController = new ExportController(controller);

      logController = new LogController({
        // Use logging config if provided or use default one (validation will fill an empty hash with default values).
        config: interactive.logging || validator.validateCompleteness(metadata.logging, {}),
        componentByID: componentByID,
        // List of components that implement .enableLogging() method and should have logging enabled by default.
        additionalComponents: [aboutDialog, shareDialog, creditsDialog, helpSystem],
        boundProperties: getBoundProperties(),
        interactivesController: controller
      });

      // Setup experimentController, if defined.
      if (interactive.experiment) {
        experimentController = new ExperimentController(interactive.experiment, controller);
      }

      // When all components are created, we can initialize semantic layout.
      setupLayout();
    }

    function createModelController(type, modelUrl, modelOptions) {
      // set default model type to "md2d"
      var modelType = type || "md2d";
      var modelController;

      if (ModelControllerFor[modelType] === null) {
        throw new Error("Couldn't understand modelType '" + modelType + "'!");
      }

      modelController = new ModelControllerFor[modelType](modelUrl, modelOptions, controller);

      return modelController;
    }

    /**
      Load the model from the model definitions hash.

      @param: currentModelID.
      @optionalParam modelObject
      @optionalParam additionalPropertiesToRetain properties that should be retained during load
                     process except from ones defined in 'propertiesToRetain' interactive section
      @optionalParam cause cause of the load (can be load, reload or custom)
    */
    function loadModel(id, modelConfig, additionalPropertiesToRetain, cause) {
      var modelDefinition = getModelDefinition(id),
          interactiveViewOptions,
          interactiveModelOptions,
          retainedProperties;

      // Ensure that model load is always the same if it's desired ("randomSeed" parameter
      // is provided).
      generateRandomSeed();

      // Check initialModelLoad flag. If it's equal to false, it means that it's a subsequent model
      // load and properties really can be retained.
      if (!initialModelLoad) {
        retainedProperties = getRetainedProperties(additionalPropertiesToRetain);
      }

      currentModelID = id;
      controller.currentModel = modelDefinition;

      if (modelDefinition.viewOptions) {
        // Make a deep copy of modelDefinition.viewOptions, so we can freely mutate interactiveViewOptions
        // without the results being serialized or displayed in the interactives editor.
        interactiveViewOptions = $.extend(true, {}, modelDefinition.viewOptions);
      } else {
        interactiveViewOptions = { controlButtons: 'play' };
      }

      if (modelDefinition.modelOptions) {
        // Make a deep copy of modelDefinition.modelOptions.
        interactiveModelOptions = $.extend(true, {}, modelDefinition.modelOptions);
      }

      // Load provided modelConfig (highest priority), model definition placed directly inside
      // interactive JSON or model defined by URL.
      if (modelConfig) {
        finishWithLoadedModel(modelDefinition.url, modelConfig, retainedProperties, cause);
      } else if (modelDefinition.model) {
        finishWithLoadedModel(modelDefinition.url, modelDefinition.model, retainedProperties, cause);
      } else if (modelDefinition.url) {
        $.get(labConfig.modelsRootUrl + modelDefinition.url).done(function(modelConfig) {
          // Deal with the servers that return the json as text/plain
          modelConfig = typeof modelConfig === 'string' ? JSON.parse(modelConfig) : modelConfig;
          finishWithLoadedModel(modelDefinition.url, modelConfig, retainedProperties, cause);
        }).fail(function() {
          modelConfig = {
            "type": "md2d",
            "width": 2.5,
            "height": 1.5,
            "viewOptions": {
              "backgroundColor": "rgba(245,200,200,255)",
              "showClock": false,
              "textBoxes": [
                {
                  "text": "Model could not be loaded:",
                  "x": 0.0,
                  "y": 1.0,
                  "width": 2.5,
                  "height": 0.25,
                  "fontScale": 1.4,
                  "layer": 1,
                  "frame": "rectangle",
                  "textAlign": "center",
                  "strokeOpacity": 0,
                  "backgroundColor": "rgb(232,231,231)"
                },
                {
                  "text": modelDefinition.url,
                  "x": 0.0,
                  "y": 0.8,
                  "width": 2.5,
                  "height": 0.25,
                  "fontScale": 0.9,
                  "layer": 1,
                  "frame": "rectangle",
                  "textAlign": "center",
                  "strokeOpacity": 0,
                  "backgroundColor": "rgb(232,231,231)"
                }
              ]
            }
          };
          finishWithLoadedModel(modelDefinition.url, modelConfig, retainedProperties, cause);
        });
      }

      function processOptions(modelConfig, interactiveModelConfig, interactiveViewConfig) {
        var modelOptions,
            viewOptions;

        function meldOptions (base, overlay) {
          var p;
          for(p in base) {
            if (overlay[p] === undefined) {
              if (arrays.isArray(base[p])) {
                // Array.
                overlay[p] = $.extend(true, [], base[p]);
              } else if (typeof base[p] === "object") {
                // Object.
                overlay[p] = $.extend(true, {}, base[p]);
              } else {
                // Basic type.
                overlay[p] = base[p];
              }
            } else if (typeof overlay[p] === "object" && !(overlay[p] instanceof Array)) {
              overlay[p] = meldOptions(base[p], overlay[p]);
            } else if (overlay[p] instanceof Array && base[p] instanceof Array) {
              overlay[p] = $.extend(true, base[p], overlay[p]);
            }
          }
          return overlay;
        }

        // 1. Process view options.
        // Do not modify initial configuration.
        viewOptions = $.extend(true, {}, interactiveViewConfig);
        // Merge view options defined in interactive (interactiveViewConfig)
        // with view options defined in the basic model description.
        viewOptions = meldOptions(modelConfig.viewOptions || {}, viewOptions);

        // 2. Process model options.
        // Do not modify initial configuration.
        modelOptions = $.extend(true, {}, interactiveModelConfig);
        // Merge model options defined in interactive (interactiveModelConfig)
        // with the basic model description.
        modelOptions = meldOptions(modelConfig || {}, modelOptions);

        // Update view options in the basic model description after merge.
        // Note that many unnecessary options can be passed to Model constructor
        // because of that (e.g. view-only options defined in the interactive).
        // However, all options which are unknown for Model will be discarded
        // during options validation, so this is not a problem
        // (but significantly simplifies configuration).
        modelOptions.viewOptions = viewOptions;

        return modelOptions;
      }

      function finishWithLoadedModel(modelUrl, modelConfig, retainedProperties, cause) {
        // Save initial model config for reload method (so it can be synchronous).
        initialModelConfig = $.extend(true, {}, modelConfig);

        var modelOptions = processOptions(modelConfig, interactiveModelOptions, interactiveViewOptions);
        var possiblySetButtonStyle;

        if (modelController) {
          modelController.reload(modelUrl, modelOptions, true);
        } else {
          throw new Error("REF something went wrong");
        }

        model = modelController.model;

        setupModelPlayerKeyboardHandler();

        // Update model references in various objects.
        scriptingAPI.bindModel(model);
        // FIXME: this doesn't seem like a necessary step. However, without it md2d-scripting-api
        // tests fail, but only when all tests are run (e.g. make test-src)! When you run just this
        // single test everything works (e.g. mocha test/md2d/md2d-scripting-api). It looks like
        // window.script variable references some old API instance and seems to be related to the
        // test environment setup. Temporarily put this call here for safety.
        scriptingAPI.exposeScriptingAPI();
        parentMessageAPI.bindModel(model);

        initializeModelOutputsAndParameters();

        if (retainedProperties) {
          model.set(retainedProperties);
        }

        for (var i = 0; i < dataSetModelLoadedCallbacks.length; i++) {
          dataSetModelLoadedCallbacks[i]();
        }

        // We call component loaded callbacks before onLoad scripts because some onLoad scripts
        // may require that components are already initialized.
        for (i = 0; i < componentModelLoadedCallbacks.length; i++) {
          componentModelLoadedCallbacks[i](model, scriptingAPI);
        }

        var onLoadScript = null;
        if (controller.currentModel.onLoad) {
          onLoadScript = scriptingAPI.makeFunctionInScriptContext(getStringFromArray(controller.currentModel.onLoad));
          onLoadScript();
        }

        if (experimentController) {
          experimentController.setOnLoadScript(onLoadScript);
        }

        // Setup exporter. Has to happen before modelLoaded. Time-based dependencies FTW.
        exportController.init(interactive.exports || getDefaultExports());

        exportController.on('canExportData.interactivesController', function() {
          // hacky, but this updates the 'actualUseDuration' output which depends on canExportData
          modelController.model.makeInvalidatingChange();
        });

        if (exportController.modelCanExportData()) {
          modelController.model.properties.controlButtonStyle = 'codap';
        }

        exportController.on('modelCanExportData.interactivesController', function() {
          modelController.model.properties.controlButtonStyle = 'codap';
        });

        dispatch.modelLoaded(cause || "initialLoad");

        // Call .ready *after* all previous operations. Note that it will trigger e.g. tick
        // history push of an initial state. It should include all modifications applied
        // by on load scripts and callbacks.
        if (model.ready) model.ready();

        // This will attach model container to DOM.
        semanticLayout.setupModel(modelController);
        layoutInteractive();

        modelController.initializeView();

        dispatch.interactiveRendered();

        // Each subsequent model won't be treated as initial one so e.g. properties can be retained.
        initialModelLoad = false;
      }
    }

    function initializeModelOutputsAndParameters() {
      setupCustomOutputs("basic", controller.currentModel.outputs, interactive.outputs);
      setupCustomParameters(controller.currentModel.parameters, interactive.parameters);
      // Setup filtered outputs after basic outputs and parameters, as filtered output require its input
      // to exist during its definition.
      setupCustomOutputs("filtered", controller.currentModel.filteredOutputs, interactive.filteredOutputs);
    }

    function modelResetHandler(cause) {
      if ( !ignoreModelResetEvent ) {
        notifyModelResetCallbacks(cause);
      }
    }

    /**
      Notify observers that a model was reset, passing along the cause of the reset event.
    */
    function notifyModelResetCallbacks(cause) {
      dispatch.modelReset(cause);
    }

    /**
      Notify observers that a reset event is pending for a given model, passing them the model that
      will be reset and a reset-request object that is unique for each observer.

      If any observers return true, the reset operation will be paused. They can cache the reset
      request object and asynchronously indicate that it is ok to proceed with the reset by calling
      the cached object's 'proceed' or 'cancel' method.

      Once the a reset request's 'proceed' method has been called, calling its 'cancel' method has
      no effect, and vice versa.

      If any observers that returned true fail to later call either method of the reset request,
      then the reset will be put off indefinitely.

      (Note that, for the time being, there is no practical difference between canceling a reset and
      putting it off indefintely. However, almost surely we will want to keep track of whether
      reset has been canceled or not, so that we can block subsequent requests to reset the model,
      give UI feedback, etc.)

      If no observers return a truthy value--and also do not call their reset request's 'cancel'
      method during their execution--then the reset will take place synchronously.

      Note that observers that do not return a truthy value are treated like observers that call the
      'proceed' method: subsequently calling the 'cancel' method of their reset request will have no
      effect.
    */
    function notifyWillResetModelAnd(closure) {

      // Fast path; also required because no callbacks => we never call resetRequest.proceed()
      if (willResetModelCallbacks.length === 0) {
        closure();
      }

      var numberOfResponsesRequired = willResetModelCallbacks.length;
      var numberOfProceedResponses = 0;
      var resetWasCanceled = false;

      function proceedIfReady() {
        if (!resetWasCanceled && numberOfProceedResponses === numberOfResponsesRequired) {
          closure();
        }
      }

      // Returns a new "use once" object that the willResetModel callback can use to asynchronously
      // allow the reset to proceed or cancel.
      function makeResetRequest() {
        var wasUsed = false;

        return {
          proceed: function() {
            if (wasUsed) {
              return;
            }
            wasUsed = true;
            numberOfProceedResponses++;
            proceedIfReady();
          },

          cancel: function() {
            if (wasUsed) {
              return;
            }
            wasUsed = true;
            resetWasCanceled = true;
          }
        };
      }

      willResetModelCallbacks.forEach(function(willResetModelCallback) {
        var resetRequest = makeResetRequest();

        // willResetModel callbacks that don't return a value (or return a falsy value) without
        // having invoked resetRequest.cancel() should be treated as having requested to proceed.
        if (!willResetModelCallback(model, resetRequest)) {
          // remember this has no effect if the callback already called resetRequest.cancel():
          resetRequest.proceed();
        }
      });
    }

    /**
      After a model loads, this method sets up the custom output properties specified in the "model"
      section of the interactive and in the interactive.

      Any output property definitions in the model section of the interactive specification override
      properties with the same that are specified in the main body if the interactive specification.

      @outputType - accept two values "basic" and "filtered", as this function can be used for processing
        both types of outputs.
    */
    function setupCustomOutputs(outputType, modelOutputs, interactiveOutputs) {
      if (!modelOutputs && !interactiveOutputs) return;

      var outputs = {},
          prop,
          output;

      function processOutputsArray(outputsArray) {
        if (!outputsArray) return;
        for (var i = 0; i < outputsArray.length; i++) {
          outputs[outputsArray[i].name] = outputsArray[i];
        }
      }

      // per-model output definitions override output definitions from interactives
      processOutputsArray(interactiveOutputs);
      processOutputsArray(modelOutputs);

      for (prop in outputs) {
        if (outputs.hasOwnProperty(prop)) {
          output = outputs[prop];
          // DOM elements (and, by analogy, Next Gen MW interactive components like slides)
          // have "ids". But, in English, properties have "names", but not "ids".
          switch (outputType) {
            case "basic":
              model.defineOutput(output.name, {
                label: output.label,
                unitType: output.unitType,
                unitName: output.unitName,
                unitPluralName: output.unitPluralName,
                unitAbbreviation: output.unitAbbreviation
              }, scriptingAPI.makeFunctionInScriptContext(getStringFromArray(output.value)));
              break;
            case "filtered":
              model.defineFilteredOutput(output.name, {
                label: output.label,
                unitType: output.unitType,
                unitName: output.unitName,
                unitPluralName: output.unitPluralName,
                unitAbbreviation: output.unitAbbreviation
              }, output.property, output.type, output.period);
              break;
          }
        }
      }
    }

    /**
      After a model loads, this method is used to set up the custom parameters specified in the
      model section of the interactive, or in the toplevel of the interactive
    */
    function setupCustomParameters(modelParameters, interactiveParameters) {
      if (!modelParameters && !interactiveParameters) return;

      var initialValues = {},
          customParameters,
          i, parameter, onChangeFunc;

      // append modelParameters second so they're processed later (and override entries of the
      // same name in interactiveParameters)
      customParameters = (interactiveParameters || []).concat(modelParameters || []);

      for (i = 0; i < customParameters.length; i++) {
        parameter = customParameters[i];
        // onChange callback is optional.
        onChangeFunc = undefined;
        if (parameter.onChange) {
          onChangeFunc = scriptingAPI.makeFunctionInScriptContext('value', getStringFromArray(parameter.onChange));
        }
        // Define parameter using modeler.
        model.defineParameter(parameter.name, {
          label: parameter.label,
          unitType: parameter.unitType,
          unitName: parameter.unitName,
          unitPluralName: parameter.unitPluralName,
          unitAbbreviation: parameter.unitAbbreviation
        }, onChangeFunc);

        if (parameter.initialValue !== undefined) {
          // Deep copy of the initial value. Otherwise, if initial value is an object or array,
          // all updates to parameter value will be shared with its initial value.
          initialValues[parameter.name] = clone(parameter.initialValue);
        }
      }

      model.set(initialValues);
    }

    function getRetainedProperties(additionalProps) {
      var propertiesToRetain;

      if ( interactive.propertiesToRetain ) {
        // interactive specifies properties to retain; use them
        propertiesToRetain = interactive.propertiesToRetain;
      } else if (exportController.canExportData()) {
        // We're in CODAP (or something similar) and interactive is not explicit about which
        // properties to retain; start with a default list of properties
        propertiesToRetain = getDefaultPropertiesToRetain();
      }

      // retain duration-related properties if they are defined
      var durationProps = ['useDuration', 'requestedDuration'].filter(function(key) {
        return model.properties[key] !== undefined;
      });
      additionalProps = (additionalProps || []).concat(durationProps);

      // Also retain properties bound to components that specify retainProperty: true, and any
      // properties in additionalProps (which come from the optional parameter to resetModel and
      // loadModel in the interactive scripting API)
      var propertyKeys = _.uniq(_.flatten([
        propertiesToRetain, propertiesRetainedByComponents, additionalProps || []
      ])).filter(isPropertyWritable);

      var properties = {};

      propertyKeys.forEach(function(key) {
        properties[key] = model.properties[key];
      });

      return properties;
    }

    function getBoundProperties() {
      var componentProperties = _.pluck(interactive.components, 'property');
      var graphProperties = _.pluck(_.where(interactive.components, { type: 'graph' }), 'properties');

      return _.without(_.uniq(_.flatten([componentProperties, graphProperties])), undefined);
    }

    function isPropertyWritable(name) {
      return !!model && !!model.isPropertyWritable && model.isPropertyWritable(name);
    }

    function getDefaultExports() {
      // start with a list of all properties bound to components
      var boundProperties = getBoundProperties();
      var additionalPerTickProperties = [];

      // assume that writable properties are experimental parameters; log them once per run
      var perRunProperties = boundProperties.filter(isPropertyWritable);

      // assume that read-only properties change due to model physics; log them at every tick
      var perTickProperties = _.difference(boundProperties, perRunProperties);

      // As a special case, MD2D models should log kinetic and potential energy (and if we are in
      // the molecular size scale, also temperature)

      if (model && model.properties.type === 'md2d') {
        additionalPerTickProperties =
          model.properties.unitsScheme === 'md2d' ?
            ['kineticEnergy', 'potentialEnergy', 'temperature'] :
            ['kineticEnergy', 'potentialEnergy'];
      }

      perTickProperties = _.uniq(perTickProperties.concat(additionalPerTickProperties));

      return {
        perRun: perRunProperties,
        perTick: perTickProperties
      };
    }

    function getDefaultPropertiesToRetain() {
      var boundProperties = getBoundProperties();
      var writableProperties = boundProperties.filter(isPropertyWritable);

      return writableProperties;
    }

    //
    // Public API.
    //
    controller = {
      get interactiveContainer() {
        // Note that by default fastclick container is returned here. It's interactive container
        // child with FastClick attached.
        // Some elements may want to attach themselves to interactive container itself when they
        // don't work well with FastClick (e.g. jQuery Context Menu).
        return $fastClickContainer;
      },
      get scriptingAPI() {
        return scriptingAPI;
      },
      get helpSystem() {
        return helpSystem;
      },
      get i18n() {
        return i18n;
      },
      get modelController() {
        return modelController;
      },
      get randomSeed() {
        return randSeed;
      },
      get exportController() {
        return exportController;
      },
      get defaultExports() {
        return getDefaultExports();
      },
      get propertiesToRetain() {
        return getRetainedProperties();
      },
      get fontFamily() {
        return $interactiveContainer.css('font-family');
      },

      /**
        Return the model object. Note this is provided behind a method because we eventually
        want to support multiple model objects accesible by id.
      */
      getModel: function() {
        return model;
      },

      /**
        Return public data set (e.g. defined in interactive JSON).
       */
      getDataSet: function(name) {
        return publicDataSetsByName[name];
      },

      /*
        Add a new data set.
        If data set is 'private', it won't be accessible via .getDataSet() method and it won't be
        serialized as a part of interactive JSON.
       */
      addDataSet: function (dataSet, private) {
        // $.proxy ensures that callback will be always executed
        // in the context of correct object ('this' binding).
        dataSetModelLoadedCallbacks.push($.proxy(dataSet.modelLoadedCallback, dataSet));
        if (!private) {
          publicDataSetsByName[dataSet.name] = dataSet;
        }
      },

      getScriptingAPI: function() {
        return scriptingAPI;
      },

      getModelController: function () {
        return modelController;
      },

      getComponent: function (id) {
        return componentByID[id];
      },

      getNextTabIndex: getNextTabIndex,

      /**
       * Loads a new interactive. It accepts an object (hash) representing interactive definition.
       * @param {object} newInteractive   new interactive definition
       */
      loadInteractive: loadInteractive,

      /**
       * Emits "interactiveRequested" event that should be handled by the embedding page.
       * It provides a new URL at which interactive is available.
       * @param {string} url              new interactive URL
       */
      requestInteractiveAt: function (url) {
        dispatch.interactiveRequested(url);
      },

      reloadInteractive: function() {
        model.stop();
        notifyWillResetModelAnd(function() {
          dispatch.interactiveWillReload();
          controller.loadInteractive(initialInteractiveConfig);
        });
      },

      loadModel: loadModel,

      /**
       * Reload the model. The interactives controller will emit a 'willResetModel'.
       * The willResetModel observers can ask to wait for asynchronous confirmation before the model
       * is actually reset; see the notifyWillResetModelAnd function.
       * @param  {object} options hash of options, supported properties:
       *                         * propertiesToRetain - a list of properties to save before
       *                           the model reload and restore after reload.
       *                         * cause - cause of the reload action, it can be e.g. "reload"
       *                           or "new-run". It will be passed to "modelLoaded" event handlers.
       */
      reloadModel: function(options) {
        if (!options) options = {};
        if (!options.cause) options.cause = "reload";

        model.stop();
        notifyWillResetModelAnd(function() {
          // Ensure that model reload is always the same if it's desired ("randomSeed" paramenter
          // is provided).
          generateRandomSeed();
          controller.loadModel(currentModelID, initialModelConfig, options.propertiesToRetain, options.cause);
        });
      },

      /**
       * Reset the model to its initial state. The interactives controller will emit
       * a 'willResetModel'. The willResetModel observers can ask to wait for asynchronous
       * confirmation before the model is actually reset; see the notifyWillResetModelAnd function.
       *
       * Once the reset is confirmed, model will issue a 'willReset' event, reset its tick history,
       * and emit a 'reset' event.
       *
       * @param {object} options hash of options, supported properties:
       *                         * propertiesToRetain - a list of properties to save before
       *                           the model reset and restore after reset.
       *                         * cause - cause of the reset action, e.g. "new-run".
      */
      resetModel: function(options) {
        model.stop();
        notifyWillResetModelAnd(function() {
          options = options || {};
          var retainedProperties = getRetainedProperties(options.propertiesToRetain);

          // Consumers of the model's events will see a reset event followed by the invalidation event
          // emitted when we set the model's parameters to their desired initial state. That's because
          // the model semantics don't include reset-with-saving-of-parameters, just reset-to-initial-
          // state. However, consumers of the interactive controller's modelReset event would expect
          // reset-to-initial-state and restoration-of-saved-parameter-values to be a single,
          // atomic event, given that they are triggered by the single
          // interactiveController.resetModel() method. (This is similar to the reason that the
          // interactive controller decorates its modelReset with a "cause" -- the model itself has no
          // notion of *why* it's reset, and doesn't distinguish "setting up an experimental run" from
          // "starting over"; those are interactive-level concepts.)
          //
          // Therefore, make sure to supress the modelReset event that would be automatically
          // emitted by our listener to the modelController's modelReset event, and emit modelReset
          // only after parameter values have been reset/restored.
          ignoreModelResetEvent = true;

          modelController.reset(options.cause);
          model.set(retainedProperties);
          notifyModelResetCallbacks(options.cause);

          ignoreModelResetEvent = false;
        });
      },

      /**
       * Logs custom event specified by author. Note that logging needs to be enabled in interactive JSON!
       * It means "logging": {"enabled": true} needs to be specified.
       * Log message will be sent to parent window, so parent window needs to handle it.
       * Currently, only LARA does it and sends logs to CC Log Manager App.
       *
       * @param {string} actionName
       * @param {object} data Hash of key-values that should be logged.
       */
      logAction: function(actionName, data) {
        logController && logController.logAction(actionName, data);
      },

      updateModelView: function() {
        modelController.updateView();
      },

      repaintModelView: function() {
        modelController.repaint();
      },

      /**
        Notifies interactive controller that the dimensions of its container have changed.
        It triggers the layout algorithm again.
      */
      resize: function () {
        layoutInteractive();
        dispatch.resize();
      },
      /**
       * Adds an event listener for the specified type. Supported events:
       * "resize", "modelLoaded", "modelReset", "interactiveRendered" and "layoutUpdated".
       *
       * @param {string} type
       * @param  {function|null} callback Callback function or null (to remove callback).
       *
       * FIXME: We should using DispatchSupport exclusively to emit events (i.e., instead of
       * maintaining custom arrays of callbacks in interactives controller, pass each callback we
       * are given to dispatch.on()). The first step would be to modify dispatchSupport to handle
       * multiple listeners for a given event, instead of reusing d3.dispatch, which is intended for
       * a simpler use case. Similar code is already checked into Lab!
       *
       * As a second step, we should modify components so that instead of defining a
       * 'modelLoadedCallback' they simply register listeners for some event. (Note that we will
       * want to create 2 events to disambiguate the 2 senses of 'modelLoaded';
       * componentModelLoadedCallbacks are called at a slightly different point in the model loading
       * sequence than 'regular' modelLoadedCallbacks.)
       */
      on: function (type, callback) {
        // Note that we can't use DispatchSupport as willResetModel event is a special one.
        // We have know number of objects interested in this event and we have to let them cancel
        // reset operation.
        if (type === "willResetModel") {
          willResetModelCallbacks.push(callback);
        } else {
          dispatch.on(type, callback);
        }
      },

      /**
       * Gets interactive property from interactive JSON definition.
       * @param  {string} name Property name.
       * @return {*}      Property value.
       */
      get: function (name) {
        return interactive[name];
      },

      /**
        Serializes interactive, returns object ready to be stringified.
        e.g. JSON.stringify(interactiveController.serialize());
      */
      serialize: function () {
        var result, i, len;
        // Copy basic properties from the initial definition, as they are immutable.
        // FIXME: this should be based on enumerating properties in the metadata. The issue is properties
        // added to the metadata like "importedFrom" have to be then manually added here.
        // NP: +1 on enumerating the metadata props here.
        result = {
          title: interactive.title,
          publicationStatus: interactive.publicationStatus,
          labEnvironment: interactive.labEnvironment,
          subtitle: interactive.subtitle,
          category: interactive.category,
          subCategory: interactive.subCategory,
          screenshot: interactive.screenshot,
          aspectRatio: interactive.aspectRatio,
          fontScale: interactive.fontScale,
          lang: interactive.lang,
          i18nMetadata: interactive.i18nMetadata,
          credits: interactive.credits,
          helpOnLoad: interactive.helpOnLoad,
          aboutOnLoad: interactive.aboutOnLoad,
          about: arrays.isArray(interactive.about) ? $.extend(true, [], interactive.about) : interactive.about,
          theme: interactive.theme,
          showTopBar: interactive.showTopBar,
          showBottomBar: interactive.showBottomBar,
          padding: interactive.padding,
          // Node that modelDefinitions section can also contain custom parameters definition. However, their initial values
          // should be already updated (take a look at the beginning of this function), so we can just serialize whole array.
          models: $.extend(true, [], interactive.models),
          propertiesToRetain: $.extend(true, [], interactive.propertiesToRetain),
          // All used parameters are already updated, they contain currently used values.
          parameters: $.extend(true, [], interactive.parameters),
          // Outputs are directly bound to the model, we can copy their initial definitions.
          outputs: $.extend(true, [], interactive.outputs),
          filteredOutputs: $.extend(true, [], interactive.filteredOutputs),
          helpTips: $.extend(true, [], interactive.helpTips)
        };

        // Update parameters' initial values.
        if (model !== undefined && model.get !== undefined) {
          // Basically, parameters can be defined in two places - in model definition object or just as a top-level
          // property of the interactive definition. Note that when particular parameter is defined in both places
          // it means that the parameter from *model* definition will be used. That's why we keep track
          // of parameters defined in the model definition.
          var modelParams = (function() {
            for (var i = 0, len = result.models.length; i < len; i++) {
              if (result.models[i].id === currentModelID) {
                return result.models[i].parameters || [];
              }
            }
            return [];
          }());
          var interactiveParams = result.parameters;
          var isModelParameter = {};
          modelParams.forEach(function (param) {
            isModelParameter[param.name] = true;
            var val = model.get(param.name);
            if (val !== undefined) {
              // Deep copy of the initial value. Otherwise, if value is an object or array, all
              // updates to parameter value will be shared with serialized value.
              param.initialValue = clone(val);
            }
          });
          interactiveParams.forEach(function (param) {
            // The parameter is overwritten by model parameter and shouldn't be updated here.
            if (isModelParameter[param.name]) return;
            var val = model.get(param.name);
            if (val !== undefined) {
              // Deep copy of the initial value. Otherwise, if value is an object or array, all
              // updates to parameter value will be shared with serialized value.
              param.initialValue = clone(val);
            }
          });
        }

        // add optional attributes to result if defined
        if (interactive.importedFrom !== undefined) {
          result.importedFrom = interactive.importedFrom;
        }

        if (interactive.exports !== undefined) {
          result.exports = $.extend(true, {}, interactive.exports);
        }

        if (interactive.logging !== undefined) {
          result.logging = $.extend(true, {}, interactive.logging);
        }

        if (interactive.experiment !== undefined) {
          result.experiment = $.extend(true, {}, interactive.experiment);
        }

        if (interactive.randomSeed !== undefined) {
          result.randomSeed = interactive.randomSeed;
        }

        // Serialize only public data sets. Data sets automatically created by components
        // (e.g. table of graph) won't be serialized as a first-class objects in interactive JSON.
        result.dataSets = [];
        for (var dsName in publicDataSetsByName) {
          result.dataSets.push(publicDataSetsByName[dsName].serialize());

        }

        // Serialize components.
        result.components = [];
        for (i = 0, len = componentList.length; i < len; i++) {
          if (componentList[i].serialize) {
            result.components.push(componentList[i].serialize());
          }
        }

        // Copy layout from the initial definition, as it is immutable.
        result.layout = $.extend(true, {}, interactive.layout);
        if (typeof interactive.template === "string") {
          result.template = interactive.template;
        } else {
          result.template = $.extend(true, [], interactive.template);
        }

        return result;
      },

      benchmarks: [
        {
          name: "interactive",
          numeric: false,
          run: function(done) {
            done(window.location.pathname + window.location.hash);
          }
        },
        {
          name: "layout (iterations)",
          numeric: true,
          formatter: d3.format("g"),
          run: function(done) {
            done(semanticLayout.layoutInteractive());
          }
        },
        {
          name: "layout (ms)",
          numeric: true,
          formatter: d3.format("5.1f"),
          run: function(done) {
            var start = +Date.now();
            semanticLayout.layoutInteractive();
            done(Date.now() - start);
          }
        }
      ],

      getLoadedModelId: function () {
        return currentModelID;
      },

      validateInteractive: validateInteractive
    };

    //
    // Initialization.
    //

    // Select interactive container.
    $interactiveContainer = $(viewSelector);
    $interactiveContainer.addClass("lab-interactive-container");

    // Initialize semantic layout.
    semanticLayout = new SemanticLayout();
    controller.on("resize.share-dialog", function () {
      shareDialog.updateIframeSize();
    });

    // Setup messaging with embedding parent window.
    parentMessageAPI = new ParentMessageAPI(controller);

    // Load interactive if it's provided.
    if (interactiveReference != null) {
      loadInteractive(interactiveReference);
    }

    return controller;
  };
});

/*global define: false, window: false */

define('grapher/public-api',['require','../lab.config','grapher/bar-graph/bar-graph-model','grapher/bar-graph/bar-graph-view','lab-grapher'],function (require) {
  'use strict';
  var
    config  = require('../lab.config'),
    BarGraphModel = require('grapher/bar-graph/bar-graph-model'),
    BarGraphView  = require('grapher/bar-graph/bar-graph-view'),
    Graph = require('lab-grapher');

  // Finally, export API to global namespace.
  // Create or get 'Lab' global object (namespace).
  window.Lab = window.Lab || {};
  // Export this API under 'grapher' name.
  window.Lab.grapher = window.Lab.grapher || {};
  window.Lab.grapher.BarGraphModel = BarGraphModel;
  window.Lab.grapher.BarGraphView = BarGraphView;
  window.Lab.grapher.Graph = Graph;
  // Export config modules.
  window.Lab.config = config;

  // Also return public API as module.
  return window.Lab.grapher;
});

/*global define */

define('import-export/netlogo-importer',['require','underscore'],function(require) {

  var _ = require('underscore'),
     fields = ["computationalInputs", "computationalOutputs", "studentInputs"];

  function columnLabelWithUnits(col) {
    return col.units ? col.label + " (" + col.units + ")" : col.label;
  }

  return {
    importRun: function(data, run) {
      if (typeof data === 'string') data = JSON.parse(data);

      return {
        perRunLabels: _.flatten(fields.map(function (field) {
          return data.description[field];
        })).map(columnLabelWithUnits),

        perRunValues: _.flatten(fields.map(function (field) {
          return data.runs[run][field];
        })),

        perTickLabels: data.description.timeSeriesData.map(columnLabelWithUnits),

        // Just a fancy way of copying the nested arrays
        perTickValues: data.runs[run].timeSeriesData.map(function (list) {
          return list.map(_.identity);
        })
      };
    },

    numberOfRuns: function(data) {
      if (typeof data === 'string') data = JSON.parse(data);
      return data.runs.length;
    },

    timeStamps: function(data) {
      if (typeof data === 'string') data = JSON.parse(data);
      return _.pluck(data.runs, 'timeStamp');
    },

    runHavingTimeStamp: function(data, timeStamp) {
      if (typeof data === 'string') data = JSON.parse(data);

      // Would be nice to have a generator expression/lazy-list equivalent, so we could use 'pluck'
      // and 'indexOf' without having to iterate over all runs before filtering the first one.

      for (var i = 0; i < data.runs.length; i++) {
        if (data.runs[i].timeStamp === timeStamp) {
          return i;
        }
      }
      return null;

    }

  };
});

/*global define: false, window: false */

define('import-export/public-api',['require','import-export/dg-exporter','import-export/netlogo-importer'],function (require) {
  'use strict';

  window.Lab = window.Lab || {};

  return window.Lab.importExport = {
    version: "0.0.1",
    // ==========================================================================
    // Functions and modules which should belong to this API:

    // Data Games exporter
    dgExporter:      require('import-export/dg-exporter'),
    netlogoImporter: require('import-export/netlogo-importer')
    // ==========================================================================
  };
});

/*global define: false */

define('public-api',['require','lab.version','lab.config','common/controllers/interactives-controller','common/benchmark/benchmark','grapher/public-api','import-export/public-api'],function (require) {
  var version = require('lab.version'),
      config  = require('lab.config'),
      InteractivesController  = require('common/controllers/interactives-controller'),
      benchmark               = require('common/benchmark/benchmark');

  // Require public-api modules.
  require('grapher/public-api');
  require('import-export/public-api');

  // Create or get 'Lab' global object (namespace).
  window.Lab = window.Lab || {};
  window.Lab.version = version;
  window.Lab.config = config;
  window.Lab.InteractivesController = InteractivesController;
  window.Lab.benchmark = benchmark;
});

require(['public-api'], undefined, undefined, true); }());
