SimpleSim = {}; exports = SimpleSim;

(function(exports) {

  /** @namespace */
  var System = {
	  name: 'System'
	};

  /**
   * Stores references to all items in the system.
   * @private
   */
	System._records = {
	  lookup: {},
	  list: []
	};

  /**
   * Used to create unique ids.
   * @private
   */
	System._idCount = 0;

  /**
   * Holds a transform property based on supportedFeatures.
   * @private
   */
  System._stylePosition = '';

  /**
   * Increments idCount and returns the value.
   */
  System.getNewId = function() {
    this._idCount++;
    return this._idCount;
  };

  /**
   * Initializes the system and starts the update loop.
   *
   * @param {Function} opt_setup= Creates the initial system conditions.
   * @param {Object} opt_world= A reference to a DOM element representing the System world.
   * @param {Function} opt_supportedFeatures= A map of supported browser features.
   */
	System.init = function(opt_setup, opt_world, opt_supportedFeatures) {

		var setup = opt_setup || function () {},
      		world = opt_world || document.body,
          supportedFeatures = opt_supportedFeatures || null;

    if (supportedFeatures.csstransforms3d) {
      this._stylePosition = '-webkit-transform: translate3d(<x>px, <y>px, 0) rotate(<a>deg); -moz-transform: translate3d(<x>px, <y>px, 0) rotate(<a>deg); -o-transform: translate3d(<x>px, <y>px, 0) rotate(<a>deg); -ms-transform: translate3d(<x>px, <y>px, 0) rotate(<a>deg);';
    } else if (supportedFeatures.csstransforms) {
      this._stylePosition = '-webkit-transform: translate(<x>px, <y>px) rotate(<a>deg); -moz-transform: translate(<x>px, <y>px) rotate(<a>deg); -o-transform: translate(<x>px, <y>px) rotate(<a>deg); -ms-transform: translate(<x>px, <y>px) rotate(<a>deg);';
    } else {
      this._stylePosition = 'position: absolute; left: <x>px; top: <y>px;';
    }

    System._records.list.push(new exports.World(world));

    exports.Utils._addEvent(window, 'resize', function(e) {
      System._resize.call(System, e);
    });

    exports.Utils._addEvent(window, 'devicemotion', function(e) {

      var world = System._records.list[0],
          x = e.accelerationIncludingGravity.x,
          y = e.accelerationIncludingGravity.y;

      if (window.orientation === 0) {
        world.gravity.x = x;
        world.gravity.y = y * -1;
      } else if (window.orientation === -90) {
        world.gravity.x = y;
        world.gravity.y = x;
      } else {
        world.gravity.x = y * -1;
        world.gravity.y = x * -1;
      }
    });

    exports.Utils._addEvent(window, 'keyup', function(e) {
      System._keyup.call(System, e);
    });

    System.mouse = {
      location: new exports.Vector(),
      lastLocation: new exports.Vector(),
      velocity: new exports.Vector()
    };
    exports.Utils._addEvent(document, 'mousemove', function(e) {
      System._recordMouseLoc.call(System, e);
    });

		setup.call(this);
    this._setup = setup;
    this._update();


	};

  /**
   * Handles keyup events.
   *
   * @param {Object} e An event.
   */
  System._keyup = function(e) {

    var world = this._records.list[0];

    switch(e.keyCode) {
      case 39:
        System._stepForward(); // right arrow: step forward
      break;
      case 80: // p; pause/play
        world.pauseStep = !world.pauseStep;
        break;
      case 82: // r; reset
        System._resetSystem();
        break;
    }
  };

  /**
   * Saves the mouse velocity and location relative to the browser viewport.
   * @param {Object} e An event.
   * @private
   */
  System._recordMouseLoc = function(e) {
    this.mouse.lastLocation.x = this.mouse.location.x;
    this.mouse.lastLocation.y = this.mouse.location.y;
    if (e.pageX && e.pageY) {
      this.mouse.location.x = e.pageX;
      this.mouse.location.y = e.pageY;
    } else if (e.clientX && e.clientY) {
      this.mouse.location.x = e.clientX;
      this.mouse.location.y = e.clientY;
    }
    this.mouse.velocity.x = this.mouse.lastLocation.x - this.mouse.location.x;
    this.mouse.velocity.y = this.mouse.lastLocation.y - this.mouse.location.y;
  };

  /**
   * Adds an object to the system.
   *
   * @param {Object} opt_options= Object properties.
   */
  System.add = function(klass, opt_options) {

    var last, records = this._records.list,
        recordsLookup = this._records.lookup,
        options = opt_options || {};

    options.world = records[0];

    if (exports[klass]) {
      records[records.length] = new exports[klass](options);
    } else if (exports.Classes[klass]) {
      records[records.length] = new exports.Classes[klass](options);
    } else {
      throw new Error(klass + ' class does not exist.');
    }

    last = records.length - 1;
    recordsLookup[records[last].id] = records[last].el.parentNode;
    records[last].init(options);
    return records[last];
  };

  /**
   * Iterates over objects in the system and calls step() and draw().
   * @private
   */
  System._update = function() {

    var i, records = System._records.list, record;

    for (i = records.length - 1; i >= 0; i -= 1) {
      record = records[i];
      if (!record.world.pauseStep) {
        record.step();
      }
    }

    for (i = records.length - 1; i >= 0; i -= 1) {
      records[i].draw();
    }
    window.requestAnimFrame(System._update);
  };

  /**
   * Pauses the system and processes one step in records.
   * @private
   */
  System._stepForward = function() {

    var i, records = System._records.list,
        world = this._records.list[0];

    world.pauseStep = true;

    for (i = records.length - 1; i >= 0; i -= 1) {
      records[i].step();
    }
    for (i = records.length - 1; i >= 0; i -= 1) {
      records[i].draw();
    }
  };

  /**
   * Updates the corresponding DOM element's style property.
   */
  System._draw = function(obj) {

    var cssText = exports.System.getCSSText({
      x: obj.location.x - (obj.width / 2),
      y: obj.location.y - (obj.height / 2),
      width: obj.width,
      height: obj.height,
      color0: obj.color[0],
      color1: obj.color[1],
      color2: obj.color[2],
      visibility: obj.visibility,
      borderRadius: obj.borderRadius,
      a: obj.angle
    });
    obj.el.style.cssText = cssText;
  };

  /**
   * Concatenates a new cssText string.
   *
   * @param {Object} props A map of object properties.
   */
  System.getCSSText = function(props) {
    return this._stylePosition.replace(/<x>/g, props.x).replace(/<y>/g, props.y).replace(/<a>/g, props.a) + ' width: ' +
        props.width + 'px; height: ' + props.height + 'px; background-color: ' +
        'rgb(' + props.color0 + ', ' + props.color1 + ', ' + props.color2 + ');' +
        'visibility: ' + props.visibility + '; border-radius: ' + props.borderRadius + '%';
  };

  /**
   * Repositions all items relative to the viewport size and resets the world bounds.
   */
  System._resize = function() {

    var i, max, records = this._records.list, record,
        viewportSize = exports.Utils.getViewportSize(),
        world = records[0];

    for (i = 1, max = records.length; i < max; i++) {
      record = records[i];
      record.location.x = viewportSize.width * (record.location.x / world.width);
      record.location.y = viewportSize.height * (record.location.y / world.height);
    }

    world.width = viewportSize.width;
    world.height = viewportSize.height;
    world.location = new exports.Vector((viewportSize.width / 2),
      (viewportSize.height / 2));
  };

  /**
   * Resets the system.
   *
   * @param {boolean} opt_noRestart= Pass true to not restart the system.
   * @private
   */
  System._resetSystem = function(opt_noRestart) {

    var world = this._records.list[0],
        viewportSize = exports.Utils.getViewportSize();

    world.pauseStep = false;
    while(world.el.firstChild) {
      world.el.removeChild(world.el.firstChild);
    }
    world.location = new exports.Vector((viewportSize.width / 2),
      (viewportSize.height / 2));
    this._records.list = this._records.list.splice(0, 1);
    System._setup.call(System);
  };

	exports.System = System;

}(exports));


(function(exports) {

  /**
   * Creates a new World.
   *
   * @param {Object} el The DOM element representing the world.
   * @constructor
   */
	function World(el) {

    var viewportSize = exports.Utils.getViewportSize();

    if (!el || typeof el !== 'object') {
      throw new Error('World: A valid DOM object is required for a new World.');
    }

    this.el = el;
    this.el.className = 'world';
    this.width = viewportSize.width;
    this.height = viewportSize.height;
    this.location = new exports.Vector(viewportSize.width / 2, viewportSize.height / 2);
    this.angle = 0;
    this.gravity = new exports.Vector(0, 0.1);
    this.wind = new exports.Vector();
    this.thermal = new exports.Vector(0, -0.025);
    this.color = [230, 230, 230];
    this.visibility ='visible';
    this.cacheVector = new exports.Vector();
    this.pauseStep = false;
    this.camera = new exports.Vector();
  }

  /**
   * Worlds do not have worlds. However, assigning a
   * blank object avoid coding extra logic in System._update.
   */
  World.prototype.world = {};

  /**
   * Updates properties.
   */
  World.prototype.step = function() {
    var dx = exports.System.mouse.location.x - this.width / 2,
        dy = exports.System.mouse.location.y - this.height / 2;
    this.gravity.x = dx * 0.01;
    this.gravity.y = dy * 0.05;
  };

  /**
   * Updates the corresponding DOM element's style property.
   */
  World.prototype.draw = function() {
    exports.System._draw(this);
  };

	exports.World = World;

}(exports));


(function(exports) {

  /**
   * Creates a new Item.
   *
   * @param {Object} options A map of initial properties.
   * @constructor
   */
  function Item(options) {

    if (!options || !options.world || typeof options.world !== 'object') {
      throw new Error('Item: A valid DOM object is required for a new Item.');
    }

    this.world = options.world;
    this.name = options.name || 'Item';
    this.id = this.name + exports.System.getNewId();

    this.el = document.createElement('div');
    this.el.id = this.id;
    this.el.className = 'item ' + this.name.toLowerCase();
    this.el.style.visibility = 'hidden';
    this.world.el.appendChild(this.el);
  }

  /**
   * Initializes the object.
   * @param {Object} options= A map of initial properties.
   */
  Item.prototype.init = function(opt_options) {

    var options = opt_options || {};

    this.acceleration = options.acceleration || new exports.Vector();
    this.velocity = options.velocity || new exports.Vector();
    this.location = options.location || new exports.Vector(this.world.width / 2, this.world.height / 2);
    this.width = options.width || 20;
    this.height = options.height || 20;
    this.mass = (this.width * this.height) * 0.025;
    this.color = options.color || [0, 0, 0];
    this.visibility = options.visibility || 'visible';
    this.maxSpeed = options.maxSpeed || 5;
    this.bounciness = options.bounciness || 0.75;
    this.borderRadius = options.borderRadius || 0;
    this.angle = options.angle || 0;
    this.checkWorldEdges = options.checkWorldEdges === undefined ? true : options.checkWorldEdges;
    this.controlCamera = options.controlCamera === undefined ? false : options.controlCamera;
  };

  /**
   * Updates properties.
   */
  Item.prototype.step = function() {
    this.applyForce(this.world.wind);
    this.applyForce(this.world.thermal);
    this.applyForce(this.world.gravity);
    this.velocity.add(this.acceleration);
    this.velocity.limit(this.maxSpeed);
    if (this.checkWorldEdges) {
      this._checkWorldEdges();
    }
    if (this.controlCamera) {
      this._checkCameraEdges();
    }
    this.location.add(this.velocity);
    this.angle = this.location.x;
    this.acceleration.mult(0);
  };

  /**
   * Adds a force to this object's acceleration.
   *
   * @param {Object} force A Vector representing a force to apply.
   */
  Item.prototype.applyForce = function(force) {
    var vector = this.world.cacheVector;
    vector.x = force.x;
    vector.y = force.y;
    vector.div(this.mass);
    this.acceleration.add(vector);
  };

  /**
   * Determines if this object is outside the world bounds.
   * @private
   */
  Item.prototype._checkWorldEdges = function() {

    var world = this.world,
        location = this.location,
        velocity = this.velocity,
        width = this.width,
        height = this.height,
        bounciness = this.bounciness;

    if (location.x + width / 2 > world.width) {
      location.x = world.width - width / 2;
      velocity.x *= -1 * bounciness;
    } else if (location.x < width / 2) {
      location.x = width / 2;
      velocity.x *= -1 * bounciness;
    }

    if (location.y + height / 2 > world.height) {
      location.y = world.height - height / 2;
      velocity.y *= -1 * bounciness;
    } else if (location.y < height / 2) {
      location.y = height / 2;
      velocity.y *= -1 * bounciness;
    }
  };

  /**
   * Moves the world in the opposite direction of the item.
   */
  Item.prototype._checkCameraEdges = function() {
    this.world.camera.x = this.velocity.x;
    this.world.camera.y = this.velocity.y;
    this.world.location.add(this.world.camera.mult(-1));
  };

  /**
   * Updates the corresponding DOM element's style property.
   */
  Item.prototype.draw = function() {
    exports.System._draw(this);
  };

  exports.Item = Item;

}(exports));


(function(exports) {

  var Utils = {};

  /**
   * Determines the size of the browser viewport.
   *
   * @returns {Object} The current browser viewport width and height.
   * @private
   */
  Utils.getViewportSize = function() {

    var d = {};

    if (typeof(window.innerWidth) !== 'undefined') {
      d.width = window.innerWidth;
      d.height = window.innerHeight;
    } else if (typeof(document.documentElement) !== 'undefined' &&
        typeof(document.documentElement.clientWidth) !== 'undefined') {
      d.width = document.documentElement.clientWidth;
      d.height = document.documentElement.clientHeight;
    } else if (typeof(document.body) !== 'undefined') {
      d.width = document.body.clientWidth;
      d.height = document.body.clientHeight;
    } else {
      d.width = undefined;
      d.height = undefined;
    }
    return d;
  };

  /**
   * Adds an event listener.
   *
   * @param {Object} target The element to receive the event listener.
   * @param {string} eventType The event type.
   * @param {function} The function to run when the event is triggered.
   * @private
   */
  Utils._addEvent = function(target, eventType, handler) {
    if (target.addEventListener) { // W3C
      target.addEventListener(eventType, handler, false);
    } else if (target.attachEvent) { // IE
      target.attachEvent("on" + eventType, handler);
    }
  };

  /**
   * Extends the properties and methods of a superClass onto a subClass.
   *
   * @param {Object} subClass The subClass.
   * @param {Object} superClass The superClass.
   */
  Utils.extend = function(subClass, superClass) {
    function F() {}
    F.prototype = superClass.prototype;
    subClass.prototype = new F;
    subClass.prototype.constructor = subClass;
  };

  /**
   * Generates a psuedo-random number within a range.
   *
   * @param {number} low The low end of the range.
   * @param {number} high The high end of the range.
   * @param {boolean} [flt] Set to true to return a float.
   * @returns {number} A number.
   */
  Utils.getRandomNumber = function(low, high, flt) {
    if (flt) {
      return Math.random()*(high-(low-1)) + low;
    }
    return Math.floor(Math.random()*(high-(low-1))) + low;
  };

  exports.Utils = Utils;

}(exports));


(function(exports) {

  var Classes = {};

  exports.Classes = Classes;

}(exports));

(function(exports) {

/**
 * Creates a new Vector.
 *
 * @param {number} [opt_x = 0] The x location.
 * @param {number} [opt_y = 0] The y location.
 * @constructor
 */
function Vector(opt_x, opt_y) {
  var x = opt_x || 0,
      y = opt_y || 0;
  this.x = x;
  this.y = y;
}

/**
 * Adds a vector to this vector.
 *
 * @param {Object} vector The vector to add.
 * @returns {Object} This vector.
 */
Vector.prototype.add = function(vector) {
  this.x += vector.x;
  this.y += vector.y;
  return this;
};

/**
 * Subtracts a vector from this vector.
 *
 * @param {Object} vector The vector to subtract.
 * @returns {Object} This vector.
 */
Vector.prototype.sub = function(vector) {
  this.x -= vector.x;
  this.y -= vector.y;
  return this;
};

/**
 * Multiplies this vector by a passed value.
 *
 * @param {number} n Vector will be multiplied by this number.
 * @returns {Object} This vector.
 */
Vector.prototype.mult = function(n) {
  this.x *= n;
  this.y *= n;
  return this;
};

/**
 * Divides this vector by a passed value.
 *
 * @param {number} n Vector will be divided by this number.
 * @returns {Object} This vector.
 */
Vector.prototype.div = function(n) {
  this.x = this.x / n;
  this.y = this.y / n;
  return this;
};

/**
 * Calculates the magnitude of this vector.
 *
 * @returns {number} The vector's magnitude.
 */
Vector.prototype.mag = function() {
  return Math.sqrt((this.x * this.x) + (this.y * this.y));
};

/**
 * Limits the vector's magnitude.
 *
 * @param {number} opt_high The upper bound of the vector's magnitude
 * @param {number} opt_low The lower bound of the vector's magnitude.
 * @returns {Object} This vector.
 */
Vector.prototype.limit = function(opt_high, opt_low) {
  var high = opt_high || null,
      low = opt_low || null;
  if (high && this.mag() > high) {
    this.normalize();
    this.mult(high);
  }
  if (low && this.mag() < low) {
    this.normalize();
    this.mult(low);
  }
  return this;
};

/**
 * Divides a vector by its magnitude to reduce its magnitude to 1.
 * Typically used to retrieve the direction of the vector for later manipulation.
 *
 * @returns {Object} This vector.
 */
Vector.prototype.normalize = function() {
  var m = this.mag();
  if (m !== 0) {
    return this.div(m);
  }
};

/**
 * Rotates a vector using a passed angle in radians.
 *
 * @param {number} radians The angle to rotate in radians.
 * @returns {Object} This vector.
 */
Vector.prototype.rotate = function(radians) {
  var cos = Math.cos(radians),
    sin = Math.sin(radians),
    x = this.x,
    y = this.y;

  this.x = x * cos - y * sin;
  this.y = x * sin + y * cos;
  return this;
};

exports.Vector = Vector;

}(exports));

/**
 * RequestAnimationFrame shim layer with setTimeout fallback
 * @param {function} callback The function to call.
 * @returns {function|Object} An animation frame or a timeout object.
 */
window.requestAnimFrame = (function(callback){

  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          window.oRequestAnimationFrame      ||
          window.msRequestAnimationFrame     ||
          function(callback) {
            window.setTimeout(callback, 1000 / 60);
          };
})();