//     Quintus Game Engine
//     (c) 2012 Pascal Rettig, Cykod LLC
//     Quintus may be freely distributed under the MIT license or GPLv2 License.
//     For all details and documentation:
//     http://html5quintus.com
//
// Quintus HTML5 Game Engine 
// =========================
//
// The code in `quintus.js` defines the base `Quintus()` method
// which create an instance of the engine. The basic engine doesn't
// do a whole lot - it provides an architecture for extension, a
// game loop, and a method for creating or binding to an exsiting
// canvas context. The engine has dependencies on Underscore.js and jQuery,
// although the jQuery dependency will be removed in the future.
//
// Most of the game-specific functionality is in the 
// various other modules:
//
// * `quintus_input.js` - `Input` module, which allows for user input via keyboard and touchscreen
// * `quintus_sprites.js` - `Sprites` module, which defines a basic `Q.Sprite` class along with spritesheet support in `Q.SpriteSheet`.
// * `quintus_scenes.js` - `Scenes` module. It defines the `Q.Scene` class, which allows creation of reusable scenes, and the `Q.Stage` class, which handles managing a number of sprites at once.
// * `quintus_anim.js` - `Anim` module, which adds in support for animations on sprites along with a `viewport` component to follow the player around and a `Q.Repeater` class that can create a repeating, scrolling background.


// Engine Bootstrapping
// ====================

// Top-level Quintus engine factory wrapper, 
// creates new instances of the engine by calling:
//
//      var Q = Quintus({  ...  });
//
// Any initial setup methods also all return the `Q` object, allowing any initial 
// setup calls to be chained together.
//
//      var Q = Quintus()
//              .include("Input, Sprites, Scenes")
//              .setup('quintus', { maximize: true })
//              .controls();
//                       
// `Q` is used internally as the object name, and is used in most of the examples, 
// but multiple instances of the engine on the same page can have different names.
//
//     var Game1 = Quintus(), Game2 = Quintus();
//
var Quintus = function Quintus(opts) {

  // A la jQuery - the returned `Q` object is actually
  // a method that calls `Q.select`. `Q.select` doesn't do anything
  // initially, but can be overridden by a module to allow
  // selection of game objects. The `Scenes` module adds in 
  // the select method which selects from the default stage.
  //
  //     var Q = Quintus().include("Sprites, Scenes");
  //     ... Game Code ...
  //     // Set the angry property on all Enemy1 class objects to true
  //     Q("Enemy1").p({ angry: true });
  //     
  //
  var Q = function(selector,scope,options) {   
    return Q.select(selector,scope,options);
  };

  Q.select = function() { /* No-op */ };

  // Syntax for including other modules into quintus, can accept a comma-separated
  // list of strings, an array of strings, or an array of actual objects. Example:
  //
  //     Q.include("Input, Sprites, Scenes")
  //
  Q.include = function(mod) {
    Q._each(Q._normalizeArg(mod),function(name) {
      var m = Quintus[name] || name;
      if(!Q._isFunction(m)) { throw "Invalid Module:" + name; }
      m(Q);
    });
    return Q;
  };

  // Utility Methods
  // ===============
  //
  // Most of these utility methods are a subset of Underscore.js,
  // Most are pulled directly from underscore and some are
  // occasionally optimized for speed and memory usage in lieu of flexibility.
  // Underscore.js is (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
  // Underscore is freely distributable under the MIT license.
  // http://underscorejs.org

  // An internal utility method (utility methods are prefixed with underscores)
  // It's used to take a string of comma separated names and turn it into an `Array`
  // of names. If an array of names is passed in, it's left as is. Example usage:
  //
  //     Q._normalizeArg("Sprites, Scenes, Physics   ");
  //     // returns [ "Sprites", "Scenes", "Physics" ]
  //
  // Used by `Q.include` and `Q.Sprite.add` to add modules and components, respectively.
  Q._normalizeArg = function(arg) {
    if(Q._isString(arg)) {
      arg = arg.replace(/\s+/g,'').split(",");
    }
    if(!Q._isArray(arg)) {
      arg = [ arg ];
    }
    return arg;
  };


  // Extends a destination object
  // with a source object
  Q._extend = function(dest,source) {
    if(!source) { return dest; }
    for (var prop in source) {
      dest[prop] = source[prop];
    }
    return dest;
  };

  // Return a shallow copy of an object. Sub-objects (and sub-arrays) are not cloned.
  Q._clone = function(obj) {
    return Q._extend({},obj);
  };

  // Method that adds default properties onto
  // an object only if the key is undefined
  Q._defaults = function(dest,source) {
    if(!source) { return dest; }
    for (var prop in source) {
      if(dest[prop] === void 0) {
        dest[prop] = source[prop];
      }
    }
    return dest;
  };

  // Shortcut for hasOwnProperty
  Q._has = function(obj, key) {
    return Object.prototype.hasOwnProperty.call(obj, key);
  };

  // Check if something is a string
  // NOTE: this fails for non-primitives
  Q._isString = function(obj) {
    return typeof obj === "string";
  };

  Q._isNumber = function(obj) {
    return Object.prototype.toString.call(obj) === '[object Number]';
  };

  // Check if something is a function
  Q._isFunction = function(obj) {
    return Object.prototype.toString.call(obj) === '[object Function]';
  };

  // Check if something is a function
  Q._isObject = function(obj) {
    return Object.prototype.toString.call(obj) === '[object Object]';
  };

  // Check if something is a function
  Q._isArray = function(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
  };

  // Check if something is undefined
  Q._isUndefined = function(obj) {
    return obj === void 0;
  };

  // Removes a property from an object and returns it
  Q._popProperty = function(obj,property) {
    var val = obj[property];
    delete obj[property];
    return val;
  };

  // Basic iteration method. This can often be a performance
  // handicap when the callback iterator is created inline,
  // as this leads to lots of functions that need to be GC'd.
  // Better is to define the iterator as a private method so
  // it is only created once.
  Q._each = function(obj,iterator,context) {
    if (obj == null) { return; }
    if (obj.forEach) {
      obj.forEach(iterator,context);
    } else if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        iterator.call(context, obj[i], i, obj);
      }
    } else {
      for (var key in obj) {
        iterator.call(context, obj[key], key, obj);
      }
    }
  };

  // Invoke the named property on each element of the array
  Q._invoke= function(arr,property,arg1,arg2) {
    if (arr == null) { return; }
    for (var i = 0, l = arr.length; i < l; i++) {
      arr[i][property](arg1,arg2);
    }
  };



  // Basic detection method, returns the first instance where the
  // iterator returns truthy. 
  Q._detect = function(obj,iterator,context,arg1,arg2) {
    var result;
    if (obj == null) { return; }
    if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        result = iterator.call(context, obj[i], i, arg1,arg2);
        if(result) { return result; }
      }
      return false;
    } else {
      for (var key in obj) {
        result = iterator.call(context, obj[key], key, arg1,arg2);
        if(result) { return result; }
      }
      return false;
    }
  };

  // Returns a new Array with entries set to the return value of the iterator.
  Q._map = function(obj, iterator, context) {
    var results = [];
    if (obj == null) { return results; }
    if (obj.map) { return obj.map(iterator, context); }
    Q._each(obj, function(value, index, list) {
      results[results.length] = iterator.call(context, value, index, list);
    });
    if (obj.length === +obj.length) { results.length = obj.length; }
    return results;
  };

  // Returns a sorted copy of unique array elements with null remove
  Q._uniq = function(arr) {
    arr = arr.slice().sort();

    var output = [];

    var last = null;
    for(var i=0;i<arr.length;i++) {
      if(arr[i] !== void 0 && last !== arr[i]) {
        output.push(arr[i]);
      }
      last = arr[i];
    }
    return output;
  };

  // returns a new array with the same entries as the source but in a random order.
  Q._shuffle = function(obj) {
    var shuffled = [], rand;
    Q._each(obj, function(value, index, list) {
      rand = Math.floor(Math.random() * (index + 1));
      shuffled[index] = shuffled[rand];
      shuffled[rand] = value;
    });
    return shuffled;
  };

  // Return an object's keys
  Q._keys = Object.keys || function(obj) {
    if(Q._isObject(obj)) { throw new TypeError('Invalid object'); }
    var keys = [];
    for (var key in obj) { if (Q._has(obj, key)) { keys[keys.length] = key; } } 
    return keys;
  };

  Q._range = function(start,stop,step) {
    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;

  };

  var idIndex = 0;
  // Return a unique identifier
  Q._uniqueId = function() {
    return idIndex++;
  };



  // Options
  // ========
  
  // Default engine options defining the paths 
  // where images, audio and other data files should be found
  // relative to the base HTML file. As well as a couple of other
  // options.
  //
  // These can be overriden by passing in options to the `Quintus()` 
  // factory method, for example:
  //
  //     // Override the imagePath to default to /assets/images/
  //     var Q = Quintus({ imagePath: "/assets/images/" });
  //
  // If you follow the default convention from the examples, however,
  // you should be able to call `Quintus()` without any options.
  Q.options = {
    imagePath: "images/",
    audioPath: "audio/",
    dataPath:  "data/",
    audioSupported: [ 'mp3','ogg' ],
    sound: true,
    frameTimeLimit: 100
  };
  if(opts) { Q._extend(Q.options,opts); }


  // Game Loop support
  // =================



  // By default the engine doesn't start a game loop until you actually tell it to.
  // Usually the loop is started the first time you call `Q.stageScene`, but if you 
  // aren't using the `Scenes` module you can explicitly start the game loop yourself
  // and control **exactly** what the engine does each cycle. For example:
  //
  //     var Q = Quintus().setup();
  //
  //     var ball = new Q.Sprite({ .. });
  //
  //     Q.gameLoop(function(dt) {
  //       Q.clear(); 
  //       ball.step(dt);
  //       ball.draw(Q.ctx);
  //     });
  //
  // The callback will be called with fraction of a second that has elapsed since 
  // the last call to the loop method.
  Q.gameLoop = function(callback) {
    Q.lastGameLoopFrame = new Date().getTime();

    // Short circuit the loop check in case multiple scenes
    // are staged immediately
    Q.loop = true; 

    // Keep track of the frame we are on (so that animations can be synced
    // to the next frame)
    Q._loopFrame = 0;

    // Wrap the callback to save it and standardize the passed
    // in time. 
    Q.gameLoopCallbackWrapper = function() {
      var now = new Date().getTime();
      Q._loopFrame++;
      Q.loop = window.requestAnimationFrame(Q.gameLoopCallbackWrapper);
      var dt = now - Q.lastGameLoopFrame;
      /* Prevent fast-forwarding by limiting the length of a single frame. */
      if(dt > Q.options.frameTimeLimit) { dt = Q.options.frameTimeLimit; }
      callback.apply(Q,[dt / 1000]);  
      Q.lastGameLoopFrame = now;
    };

    window.requestAnimationFrame(Q.gameLoopCallbackWrapper);
    return Q;
  };

  // Pause the entire game by canceling the requestAnimationFrame call. If you use setTimeout or
  // setInterval in your game, those will, of course, keep on rolling...
  Q.pauseGame = function() {
    if(Q.loop) {
      window.cancelAnimationFrame(Q.loop); 
    }
    Q.loop = null;
  };

  // Unpause the game by restarting the requestAnimationFrame-based loop.
  Q.unpauseGame = function() {
    if(!Q.loop) {
      Q.lastGameLoopFrame = new Date().getTime();
      Q.loop = window.requestAnimationFrame(Q.gameLoopCallbackWrapper);
    }
  };


  // The base Class object
  // ===============
  //
  // Quintus uses the Simple JavaScript inheritance Class object, created by
  // John Resig and described on his blog: 
  //
  // [http://ejohn.org/blog/simple-javascript-inheritance/](http://ejohn.org/blog/simple-javascript-inheritance/)
  //
  // The class is used wholesale, with the only differences being that instead
  // of appearing in a top-level namespace, the `Class` object is available as 
  // `Q.Class` and a second argument on the `extend` method allows for adding
  // class level methods and the class name is passed in a parameter for introspection
  // purposes.
  //
  // Classes can be created by calling `Q.Class.extend(name,{ .. })`, although most of the time
  // you'll want to use one of the derivitive classes, `Q.Evented` or `Q.GameObject` which
  // have a little bit of functionality built-in. `Q.Evented` adds event binding and 
  // triggering support and `Q.GameObject` adds support for components and a destroy method.
  //
  // The main things Q.Class get you are easy inheritance, a constructor method called `init()`,
  // dynamic addition of a this._super method when a method is overloaded (be careful with 
  // this as it adds some overhead to method calls.) Calls to `instanceof` also all 
  // work as you'd hope.
  //
  // By convention, classes should be added onto to the `Q` object and capitalized, so if 
  // you wanted to create a new class for your game, you'd write:
  //
  //     Q.Class.extend("MyClass",{ ... });
  //
  // Examples:
  //
  //     Q.Class.extend("Bird",{ 
  //       init: function(name) { this.name = name; },
  //       speak: function() { console.log(this.name); },
  //       fly: function()   { console.log("Flying"); }
  //     });
  //
  //     Q.Bird.extend("Penguin",{
  //       speak: function() { console.log(this.name + " the penguin"); },
  //       fly: function()   { console.log("Can't fly, sorry..."); }
  //     });
  //
  //     var randomBird = new Q.Bird("Frank"),
  //         pengy      = new Q.Penguin("Pengy");
  //
  //     randomBird.fly(); // Logs "Flying"
  //     pengy.fly();      // Logs "Can't fly,sorry..."
  //
  //     randomBird.speak(); // Logs "Frank"
  //     pengy.speak();      // Logs "Pengy the penguin"
  //
  //     console.log(randomBird instanceof Q.Bird);    // true 
  //     console.log(randomBird instanceof Q.Penguin); // false
  //     console.log(pengy instanceof Q.Bird);         // true 
  //     console.log(pengy instanceof Q.Penguin);      // true 


  /* Simple JavaScript Inheritance
   * By John Resig http://ejohn.org/
   * MIT Licensed.
   *
   * Inspired by base2 and Prototype
   */
  (function(){
    var initializing = false, 
        fnTest = /xyz/.test(function(){ var xyz;}) ? /\b_super\b/ : /.*/;
    /* The base Class implementation (does nothing) */
    Q.Class = function(){};

    // See if a object is a specific class
    Q.Class.prototype.isA = function(className) {
      return this.className === className;
    };
    
    /* Create a new Class that inherits from this class */
    Q.Class.extend = function(className, prop, classMethods) {
      /* No name, don't add onto Q */
      if(!Q._isString(className)) {
        classMethods = prop;
        prop = className;
        className = null;
      }
      var _super = this.prototype,
          ThisClass = this;
      
      /* Instantiate a base class (but only create the instance, */
      /* don't run the init constructor) */
      initializing = true;
      var prototype = new ThisClass();
      initializing = false;

      function _superFactory(name,fn) {
        return function() {
          var tmp = this._super;

          /* Add a new ._super() method that is the same method */
          /* but on the super-class */
          this._super = _super[name];

          /* The method only need to be bound temporarily, so we */
          /* remove it when we're done executing */
          var ret = fn.apply(this, arguments);        
          this._super = tmp;

          return ret;
        };
      }

      /* Copy the properties over onto the new prototype */
      for (var name in prop) {
        /* Check if we're overwriting an existing function */
        prototype[name] = typeof prop[name] === "function" && 
          typeof _super[name] === "function" && 
            fnTest.test(prop[name]) ? 
              _superFactory(name,prop[name]) : 
              prop[name];
      }
      
      /* The dummy class constructor */
      function Class() {
        /* All construction is actually done in the init method */
        if ( !initializing && this.init ) {
          this.init.apply(this, arguments);
        }
      }
      
      /* Populate our constructed prototype object */
      Class.prototype = prototype;
      
      /* Enforce the constructor to be what we expect */
      Class.prototype.constructor = Class;
      /* And make this class extendable */
      Class.extend = Q.Class.extend;
      
      /* If there are class-level Methods, add them to the class */
      if(classMethods) {
        Q._extend(Class,classMethods);
      }

      if(className) { 
        /* Save the class onto Q */
        Q[className] = Class;

        /* Let the class know its name */
        Class.prototype.className = className;
        Class.className = className;
      }
      
      return Class;
    };
  }());
    

  // Event Handling
  // ==============

  // The `Q.Evented` class adds event handling onto the base `Q.Class` 
  // class. Evented objects can trigger events and other objects can
  // bind to those events.
  Q.Class.extend("Evented",{

    // Binds a callback to an event on this object. If you provide a
    // `target` object, that object will add this event to it's list of
    // binds, allowing it to automatically remove it when it is destroyed.
    on: function(event,target,callback) {
      if(Q._isArray(event) || event.indexOf(",") !== -1) {
        event = Q._normalizeArg(event);
        for(var i=0;i<event.length;i++) {
          this.on(event[i],target,callback);
        }
        return;
      }

      // Handle the case where there is no target provided,
      // swapping the target and callback parameters.
      if(!callback) {
        callback = target;
        target = null;
      }

      // If there's still no callback, default to the event name
      if(!callback) {
        callback = event;
      }
      // Handle case for callback that is a string, this will
      // pull the callback from the target object or from this
      // object.
      if(Q._isString(callback)) {
        callback = (target || this)[callback];
      }

      // To keep `Q.Evented` objects from needing a constructor,
      // the `listeners` object is created on the fly as needed.
      // `listeners` keeps a list of callbacks indexed by event name
      // for quick lookup. 
      this.listeners = this.listeners || {};
      this.listeners[event] = this.listeners[event] || [];
      this.listeners[event].push([ target || this, callback]);

      // With a provided target, the target object keeps track of
      // the events it is bound to, which allows for automatic 
      // unbinding on destroy.
      if(target) {
        if(!target.binds) { target.binds = []; }
        target.binds.push([this,event,callback]);
      }
    },

    // Triggers an event, passing in some optional additional data about
    // the event. 
    trigger: function(event,data) {
      // First make sure there are any listeners, then check for any listeners
      // on this specific event, if not, early out.
      if(this.listeners && this.listeners[event]) {
        // Call each listener in the context of either the target passed into
        // `on` or the object itself.
        for(var i=0,len = this.listeners[event].length;i<len;i++) {
          var listener = this.listeners[event][i];
          listener[1].call(listener[0],data);
        }
      }
    },
    
    // Unbinds an event. Can be called with 1, 2, or 3 parameters, each 
    // of which unbinds a more specific listener.
    off: function(event,target,callback) {
      // Without a target, remove all teh listeners.
      if(!target) {
        if(this.listeners[event]) {
          delete this.listeners[event];
        }
      } else {
        // If the callback is a string, find a method of the
        // same name on the target.
        if(Q._isString(callback) && target[callback]) {
          callback = target[callback];
        }
        var l = this.listeners && this.listeners[event];
        if(l) {
          // Loop from the end to the beginning, which allows us
          // to remove elements without having to affect the loop.
          for(var i = l.length-1;i>=0;i--) {
            if(l[i][0] === target) {
              if(!callback || callback === l[i][1]) {
                this.listeners[event].splice(i,1);
              }
            }
          }
        }
      }
    },

    // `debind` is called to remove any listeners an object had
    // on other objects. The most common case is when an object is
    // destroyed you'll want all the event listeners to be removed
    // for you.
    debind: function() {
       if(this.binds) {
         for(var i=0,len=this.binds.length;i<len;i++) {
           var boundEvent = this.binds[i],
               source = boundEvent[0],
               event = boundEvent[1];
           source.off(event,this);
         }
       }
     }

   });


   
  // Components
  // ==============
  //
  // Components are self-contained pieces of functionality that can be added onto and removed
  // from objects. The allow for a more dynamic functionality tree than using inheritance (i.e.
  // by favoring composition over inheritance) and are added and removed on the fly at runtime.
  // (yes, I know everything in JS is at runtime, but you know what I mean, geez)
  //
  // Combining components with events makes it easy to create reusable pieces of
  // functionality that can be decoupled from each other.


  // The master list of registered components, indexed in an object by name.
  Q.components = {};

  // The base class for components. These are usually not derived directly but are instead
  // created by calling `Q.register` to register a new component given a set of methods the 
  // component supports. Components are created automatically when they are added to a 
  // `Q.GameObject` with the `add` method.
  //
  // Many components also define an `added` method, which is called automatically by the
  // `init` constructor after a component has been added to an object. This is a good time
  // to add event listeners on the object.
  Q.Evented.extend("Component",{

    // Components are created when they are added onto a `Q.GameObject` entity. The entity
    // is directly extended with any methods inside of an `extend` property and then the 
    // component itself is added onto the entity as well. 
    init: function(entity) {
      this.entity = entity;
      if(this.extend) { Q._extend(entity,this.extend);   }
      entity[this.name] = this;

      entity.activeComponents.push(this.componentName);

      if(entity.stage && entity.stage.addToList) {
        entity.stage.addToList(this.componentName,entity);
      }
      if(this.added) { this.added(); }    
    },

    // `destroy` is called automatically when a component is removed from an entity. It is 
    // not called, however, when an entity is destroyed (for performance reasons).
    // 
    // It's job is to remove any methods that were added with `extend` and then remove and
    // debind itself from the entity. It will also call `destroyed` if the component has
    // a method by that name.
    destroy: function() {
      if(this.extend) {
        var extensions = Q._keys(this.extend);
        for(var i=0,len=extensions.length;i<len;i++) {
          delete this.entity[extensions[i]];
        }
      }
      delete this.entity[this.name];
      var idx = this.entity.activeComponents.indexOf(this.componentName);
      if(idx !== -1) { 
        this.entity.activeComponents.splice(idx,1);

        if(this.entity.stage && this.entity.stage.addToList) {
          this.entity.stage.addToLists(this.componentName,this.entity);
        }
      }
      this.debind();
      if(this.destroyed) { this.destroyed(); }
    }
  });

  // This is the base class most Quintus objects are derived from, it extends 
  // `Q.Evented` and adds component support to an object, allowing components to
  // be added and removed from an object. It also defines a destroyed method
  // which will debind the object, remove it from it's parent (usually a scene)
  // if it has one, and trigger a destroyed event.
  Q.Evented.extend("GameObject",{

    // Simple check to see if a component already exists
    // on an object by searching for a property of the same name.
    has: function(component) {
      return this[component] ? true : false; 
    },


    // Adds one or more components to an object. Accepts either 
    // a comma separated string or an array of strings that map
    // to component names.
    //
    // Instantiates a new component object of the correct type
    // (if the component exists) and then triggers an addComponent
    // event.
    //
    // Returns the object to allow chaining.
    add: function(components) {
      components = Q._normalizeArg(components);
      if(!this.activeComponents) { this.activeComponents = []; }
      for(var i=0,len=components.length;i<len;i++) {
        var name = components[i],
            Comp = Q.components[name];
        if(!this.has(name) && Comp) { 
          var c = new Comp(this); 
          this.trigger('addComponent',c);
        }
      }
      return this;
    }, 

    // Removes one or more components from an object. Accepts the
    // same style of parameters as `add`. Triggers a delComponent event
    // and and calls destroy on the component.
    //
    // Returns the element to allow chaining.
    del: function(components) {
      components = Q._normalizeArg(components);
      for(var i=0,len=components.length;i<len;i++) {
        var name = components[i];
        if(name && this.has(name)) { 
          this.trigger('delComponent',this[name]);
          this[name].destroy(); 
        }
      }
      return this;
    },

    // Destroys the object by calling debind and removing the
    // object from it's parent. Will trigger a destroyed event
    // callback.
    destroy: function() {
      if(this.isDestroyed) { return; }
      this.trigger('destroyed');
      this.debind();
      if(this.stage && this.stage.remove) {
        this.stage.remove(this);
      }
      this.isDestroyed = true;
    }
  });

  // This registers a component with the engine, making it available to `Q.GameObject`'s 
  // This creates a new descendent class of `Q.Component` with new methods added in.
  Q.component = function(name,methods) {
    if(!methods) { return Q.components[name]; }
    methods.name = name;
    methods.componentName = "." + name;
    return (Q.components[name] = Q.Component.extend(name + "Component",methods));
  };


  // Canvas Methods
  // ==============
  //
  // The `setup` and `clear` method are the only two canvas-specific methods in 
  // the core of Quintus. `imageData`  also uses canvas but it can be used in
  // any type of game.


  // Setup will either create a new canvas element and append it
  // to the body of the document or use an existing one. It will then
  // pull out the width and height of the canvas for engine use.
  //
  // It also adds a wrapper container around the element.
  //
  // If the `maximize` is set to true, the canvas element is maximized
  // on the page and the scroll trick is used to try to get the address bar away.
  //
  // The engine will also resample the game to CSS dimensions at twice pixel
  // dimensions if the `resampleWidth` or `resampleHeight` options are set.
  //
  // TODO: add support for auto-resize w/ engine event notifications, remove
  // jQuery.

  Q.touchDevice = ('ontouchstart' in document);

  Q.setup = function(id, options) {
    if(Q._isObject(id)) {
      options = id;
      id = null;
    }
    options = options || {};
    id = id || "quintus";

    if(Q._isString(id)) {
      Q.el = document.getElementById(id);
    } else {
      Q.el = id;
    }

    if(!Q.el) {
      Q.el = document.createElement("canvas");
      Q.el.width = options.width || 320;
      Q.el.height = options.height || 420;
      Q.el.id = id;

      document.body.appendChild(Q.el);
    }

    var w = parseInt(Q.el.width,10),
        h = parseInt(Q.el.height,10);

    var maxWidth = options.maxWidth || 5000,
        maxHeight = options.maxHeight || 5000,
        resampleWidth = options.resampleWidth,
        resampleHeight = options.resampleHeight,
        upsampleWidth = options.upsampleWidth,
        upsampleHeight = options.upsampleHeight;

    if(options.maximize === true || (Q.touchDevice && options.maximize === 'touch'))  {
      document.body.style.padding = 0;
      document.body.style.margin = 0;

      w = Math.min(window.innerWidth,maxWidth);
      h = Math.min(window.innerHeight - 5,maxHeight);

      if(Q.touchDevice) {
        Q.el.style.height = (h*2) + "px";
        window.scrollTo(0,1);

        w = Math.min(window.innerWidth,maxWidth);
        h = Math.min(window.innerHeight,maxHeight);
      }
    }

    if((upsampleWidth && w <= upsampleWidth) ||
       (upsampleHeight && h <= upsampleHeight)) {
      Q.el.style.height = h + "px";
      Q.el.style.width = w + "px";
      Q.el.width = w * 2;
      Q.el.height = h * 2;
    }
    else if(((resampleWidth && w > resampleWidth) ||
        (resampleHeight && h > resampleHeight)) && 
       Q.touchDevice) { 
      Q.el.style.height = h + "px";
      Q.el.style.width = w + "px";
      Q.el.width = w / 2;
      Q.el.height = h / 2;
    } else {
      Q.el.style.height = h + "px";
      Q.el.style.width = w + "px";
      Q.el.width = w;
      Q.el.height = h;
    }

    var elParent = Q.el.parentNode;

    if(elParent) {
      Q.wrapper = document.createElement("div");
      Q.wrapper.id = id + '_container';
      Q.wrapper.style.width = w + "px";
      Q.wrapper.style.margin = "0 auto";
      Q.wrapper.style.position = "relative";


      elParent.insertBefore(Q.wrapper,Q.el);
      Q.wrapper.appendChild(Q.el);
    }
    
    Q.el.style.position = 'relative';

    Q.ctx = Q.el.getContext && 
            Q.el.getContext("2d");


    Q.width = parseInt(Q.el.width,10);
    Q.height = parseInt(Q.el.height,10);
    Q.cssWidth = w;
    Q.cssHeight = h;

    window.addEventListener('orientationchange',function() {
      setTimeout(function() { window.scrollTo(0,1); }, 0);
    });

    return Q;
  };


  // Clear the canvas completely.
  Q.clear = function() {
    if(Q.clearColor) {
      Q.ctx.globalAlpha = 1;
      Q.ctx.fillStyle = Q.clearColor;
      Q.ctx.fillRect(0,0,Q.width,Q.height);
    } else {
      Q.ctx.clearRect(0,0,Q.width,Q.height);
    }
  };


  // Return canvas image data given an Image object.
  Q.imageData = function(img) {
    var canvas = document.createElement("canvas");
    
    canvas.width = img.width;
    canvas.height = img.height;

    var ctx = canvas.getContext("2d");
    ctx.drawImage(img,0,0);

    return ctx.getImageData(0,0,img.width,img.height);
  };

  

  // Asset Loading Support
  // =====================
  //
  // The engine supports loading assets of different types using
  // `load` or `preload`. Assets are stored by their name so the 
  // same asset won't be loaded twice if it already exists.

  // Augmentable list of asset types, loads a specific asset 
  // type if the file type matches, otherwise defaults to a Ajax
  // load of the data.
  //
  // You can new types of assets based on file extension by
  // adding to `assetTypes` and adding a method called
  // loadAssetTYPENAME where TYPENAME is the name of the
  // type you added in.
  Q.assetTypes = { 
    png: 'Image', jpg: 'Image', gif: 'Image', jpeg: 'Image',
    ogg: 'Audio', wav: 'Audio', m4a: 'Audio', mp3: 'Audio'
  };

  // Determine the type of asset based on the lookup table above
  Q.assetType = function(asset) {
    /* Determine the lowercase extension of the file */
    var fileParts = asset.split("."),
        fileExt = fileParts[fileParts.length-1].toLowerCase();

    /* Lookup the asset in the assetTypes hash, or return other */
    return Q.assetTypes[fileExt] || 'Other';
  };

  // Loader for Images, creates a new `Image` object and uses the 
  // load callback to determine the image has been loaded
  Q.loadAssetImage = function(key,src,callback,errorCallback) {
    var img = new Image();
    img.onload = function() {  callback(key,img); };
    img.onerror = errorCallback;
    img.src = Q.options.imagePath + src;
  };


  // List of mime types given an audio file extension, used to 
  // determine what sound types the browser can play using the 
  // built-in `Sound.canPlayType`
  Q.audioMimeTypes = { mp3: 'audio/mpeg', 
                       ogg: 'audio/ogg; codecs="vorbis"',
                       m4a: 'audio/m4a',
                       wav: 'audio/wav' };

  // Loader for Audio assets. By default chops off the extension and 
  // will automatically determine which of the supported types is 
  // playable by the browser and load that type.
  //
  // Which types are available are determined by the file extensions
  // listed in the Quintus `options.audioSupported`
  Q.loadAssetAudio = function(key,src,callback,errorCallback) {
    if(!document.createElement("audio").play || !Q.options.sound) {
      callback(key,null);
      return;
    }

    var snd = new Audio(),
        baseName = Q._removeExtension(src),
        extension = null,
        filename = null;

    /* Find a supported type */
    extension = 
      Q._detect(Q.options.audioSupported,
         function(extension) {
         return snd.canPlayType(Q.audioMimeTypes[extension]) ? 
                                extension : null;
    });

    /* No supported audio = trigger ok callback anyway */
    if(!extension) {
      callback(key,null);
      return;
    }

    snd.addEventListener("error",errorCallback);
    snd.addEventListener('canplaythrough',function() { 
      callback(key,snd); 
    });
    snd.src = Q.options.audioPath + baseName + "." + extension;
    snd.load();
    return snd;
  };

  // Loader for other file types, just store the data
  // returned from an Ajax call.
  Q.loadAssetOther = function(key,src,callback,errorCallback) {
    var request = new XMLHttpRequest();

    var fileParts = src.split("."),
        fileExt = fileParts[fileParts.length-1].toLowerCase();

    request.onreadystatechange = function() {
      if(request.readyState === 4) {
        if(request.status === 200) {
          if(fileExt === 'json') {
            callback(key,JSON.parse(request.responseText));
          } else {
            callback(key,request.responseText);
          }
        } else {
          errorCallback();
        }
      }
    };

    request.open("GET",Q.options.dataPath + src, true);
    request.send(null);
  };

  // Helper method to return a name without an extension
  Q._removeExtension = function(filename) {
    return filename.replace(/\.(\w{3,4})$/,"");
  };

  // Asset hash storing any loaded assets
  Q.assets = {};


  // Getter method to return an asset by its name.
  //
  // Asset names default to their filenames, but can be overridden
  // by passing a hash to `load` to set different names.
  Q.asset = function(name) {
    return Q.assets[name];
  };

  // Load assets, and call our callback when done.
  //
  // Also optionally takes a `progressCallback` which will be called 
  // with the number of assets loaded and the total number of assets
  // to allow showing of a progress. 
  //
  // Assets can be passed in as an array of file names, and Quintus
  // will use the file names as the name for reference, or as a hash of 
  // `{ name: filename }`. 
  //
  // Example usage:
  //     Q.load(['sprites.png','sprites.,json'],function() {
  //        Q.stageScene("level1"); // or something to start the game.
  //     });
  Q.load = function(assets,callback,options) {
    var assetObj = {};

    /* Make sure we have an options hash to work with */
    if(!options) { options = {}; }

    /* Get our progressCallback if we have one */
    var progressCallback = options.progressCallback;

    var errors = false,
        errorCallback = function(itm) {
          errors = true;
          (options.errorCallback  ||
           function(itm) { throw("Error Loading: " + itm ); })(itm);
        };

    /* Convert to an array if it's a string */
    if(Q._isString(assets)) {
      assets = Q._normalizeArg(assets);
    }

    /* If the user passed in an array, convert it */
    /* to a hash with lookups by filename */
    if(Q._isArray(assets)) { 
      Q._each(assets,function(itm) {
        if(Q._isObject(itm)) {
          Q._extend(assetObj,itm);
        } else {
          assetObj[itm] = itm;
        }
      });
    } else {
      /* Otherwise just use the assets as is */
      assetObj = assets;
    }

    /* Find the # of assets we're loading */
    var assetsTotal = Q._keys(assetObj).length,
        assetsRemaining = assetsTotal;

    /* Closure'd per-asset callback gets called */
    /* each time an asset is successfully loadded */
    var loadedCallback = function(key,obj,force) {
      if(errors) { return; }

      // Prevent double callbacks (I'm looking at you Firefox, canplaythrough
      if(!Q.assets[key]||force) {

        /* Add the object to our asset list */
        Q.assets[key] = obj;

        /* We've got one less asset to load */
        assetsRemaining--;

        /* Update our progress if we have it */
        if(progressCallback) { 
           progressCallback(assetsTotal - assetsRemaining,assetsTotal); 
        }
      }

      /* If we're out of assets, call our full callback */
      /* if there is one */
      if(assetsRemaining === 0 && callback) {
        /* if we haven't set up our canvas element yet, */
        /* assume we're using a canvas with id 'quintus' */
        callback.apply(Q); 
      }
    };

    /* Now actually load each asset */
    Q._each(assetObj,function(itm,key) {

      /* Determine the type of the asset */
      var assetType = Q.assetType(itm);

      /* If we already have the asset loaded, */
      /* don't load it again */
      if(Q.assets[key]) {
        loadedCallback(key,Q.assets[key],true);
      } else {
        /* Call the appropriate loader function */
        /* passing in our per-asset callback */
        /* Dropping our asset by name into Q.assets */
        Q["loadAsset" + assetType](key,itm,
                                   loadedCallback,
                                   function() { errorCallback(itm); });
      }
    });

  };

  // Array to store any assets that need to be 
  // preloaded
  Q.preloads = [];
  
  // Let us gather assets to load at a later time,
  // and then preload them all at the same time with
  // a single callback. Options are passed through to the
  // Q.load method if used.
  //
  // Example usage:
  //      Q.preload("sprites.png");
  //      ...
  //      Q.preload("sprites.json");
  //      ...
  //
  //      Q.preload(function() {
  //         Q.stageScene("level1"); // or something to start the game
  //      });
  Q.preload = function(arg,options) {
    if(Q._isFunction(arg)) {
      Q.load(Q._uniq(Q.preloads),arg,options);
      Q.preloads = [];
    } else {
      Q.preloads = Q.preloads.concat(arg);
    }
  };


  // Math Methods
  // ==============
  //
  // Math methods, for rotating and scaling points

  // A list of matrices available
  Q.matrices2d = [];

  Q.matrix2d = function() {
    return Q.matrices2d.length > 0 ? Q.matrices2d.pop().identity() : new Q.Matrix2D();
  };

  // A 2D matrix class, optimized for 2D points,
  // where the last row of the matrix will always be 0,0,1 
  // Good Docs where: 
  //    https://github.com/heygrady/transform/wiki/calculating-2d-matrices
  Q.Matrix2D = Q.Class.extend({
    init: function(source) {

      if(source) {
        this.m = [];
        this.clone(source);
      } else {
        this.m = [1,0,0,0,1,0];
      }
    },

    // Turn this matrix into the identity
    identity: function() {
      var m = this.m;
      m[0] = 1; m[1] = 0; m[2] = 0;
      m[3] = 0; m[4] = 1; m[5] = 0;
      return this;
    },

    // Clone another matrix into this one
    clone: function(matrix) {
      var d = this.m, s = matrix.m;
      d[0]=s[0]; d[1]=s[1]; d[2] = s[2];
      d[3]=s[3]; d[4]=s[4]; d[5] = s[5];
      return this;
    },

    // a * b = 
    //   [ [ a11*b11 + a12*b21 ], [ a11*b12 + a12*b22 ], [ a11*b31 + a12*b32 + a13 ] ,
    //   [ a21*b11 + a22*b21 ], [ a21*b12 + a22*b22 ], [ a21*b31 + a22*b32 + a23 ] ]
    multiply: function(matrix) {
      var a = this.m, b = matrix.m;

      var m11 = a[0]*b[0] + a[1]*b[3];
      var m12 = a[0]*b[1] + a[1]*b[4];
      var m13 = a[0]*b[2] + a[1]*b[5] + a[2];

      var m21 = a[3]*b[0] + a[4]*b[3];
      var m22 = a[3]*b[1] + a[4]*b[4];
      var m23 = a[3]*b[2] + a[4]*b[5] + a[5];

      a[0]=m11; a[1]=m12; a[2] = m13;
      a[3]=m21; a[4]=m22; a[5] = m23;
      return this;
    },

    // Multiply this matrix by a rotation matrix rotated radians radians 
    rotate: function(radians) {
      if(radians === 0) { return this; }
      var cos = Math.cos(radians),
          sin = Math.sin(radians),
          m = this.m;

      var m11 = m[0]*cos  + m[1]*sin;
      var m12 = m[0]*-sin + m[1]*cos;

      var m21 = m[3]*cos  + m[4]*sin;
      var m22 = m[3]*-sin + m[4]*cos;

      m[0] = m11; m[1] = m12; // m[2] == m[2]
      m[3] = m21; m[4] = m22; // m[5] == m[5]
      return this;
    },

    // Helper method to rotate by a set number of degrees
    rotateDeg: function(degrees) {
      if(degrees === 0) { return this; }
      return this.rotate(Math.PI * degrees / 180);
    },

    // Multiply this matrix by a scaling matrix scaling sx and sy
    scale: function(sx,sy) {
      var m = this.m;
      if(sy === void 0) { sy = sx; }

      m[0] *= sx;
      m[1] *= sy;
      m[3] *= sx;
      m[4] *= sy;
      return this;
    },


    // Multiply this matrix by a translation matrix translate by tx and ty
    translate: function(tx,ty) {
      var m = this.m;

      m[2] += m[0]*tx + m[1]*ty;
      m[5] += m[3]*tx + m[4]*ty;
      return this;
    },

    // Memory Hoggy version
    transform: function(x,y) {
      return [ x * this.m[0] + y * this.m[1] + this.m[2], 
               x * this.m[3] + y * this.m[4] + this.m[5] ];
    },

    // Transform an object with an x and y property by this Matrix
    transformPt: function(obj) {
      var x = obj.x, y = obj.y;

      obj.x = x * this.m[0] + y * this.m[1] + this.m[2];
      obj.y = x * this.m[3] + y * this.m[4] + this.m[5];

      return obj;
    },

    // Transform an array with an x and y property by this Matrix
    transformArr: function(inArr,outArr) {
      var x = inArr[0], y = inArr[1];
      
      outArr[0] = x * this.m[0] + y * this.m[1] + this.m[2];
      outArr[1] = x * this.m[3] + y * this.m[4] + this.m[5];

      return outArr;
    },


    // Return just the x component by this Matrix
    transformX: function(x,y) {
      return x * this.m[0] + y * this.m[1] + this.m[2];
    },

    // Return just the y component by this Matrix
    transformY: function(x,y) {
      return x * this.m[3] + y * this.m[4] + this.m[5];
    },

    // Release this Matrix to be reused
    release: function() {
      Q.matrices2d.push(this);
      return null;
    },

    setContextTransform: function(ctx) {
      var m = this.m;
      // source:
      //  m[0] m[1] m[2]
      //  m[3] m[4] m[5]
      //  0     0   1
      //
      // destination:
      //  m11  m21  dx
      //  m12  m22  dy
      //  0    0    1
      //  setTransform(m11, m12, m21, m22, dx, dy)
      ctx.transform(m[0],m[3],m[1],m[4],m[2],m[5]);
      //ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
    }

  });

  // And that's it..
  // ===============
  //
  // Return the `Q` object from the `Quintus()` factory method. Create awesome games. Repeat.
  return Q;
};

// Lastly, add in the `requestAnimationFrame` shim, if necessary. Does nothing 
// if `requestAnimationFrame` is already on the `window` object.
(function() {
    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);
        };
    }
}());


