text: // ==========================================
  // Copyright 2013 Twitter, Inc
  // Licensed under The MIT License
  // http://opensource.org/licenses/MIT
  // ==========================================
  
  "use strict";
  
  define(
  
    [
      './advice',
      './utils',
      './compose',
      './registry',
      './logger',
      '../tools/debug/debug'
    ],
  
    function(advice, utils, compose, registry, withLogging, debug) {
  
      var functionNameRegEx = /function (.*?)\s?\(/;
      var componentId = 0;
  
      function teardownInstance(instanceInfo){
        instanceInfo.events.slice().forEach(function(event) {
          var args = [event.type];
  
          event.element && args.unshift(event.element);
          (typeof event.callback == 'function') && args.push(event.callback);
  
          this.off.apply(this, args);
        }, instanceInfo.instance);
      }
  
  
      function teardown() {
        teardownInstance(registry.findInstanceInfo(this));
      }
  
      //teardown for all instances of this constructor
      function teardownAll() {
        var componentInfo = registry.findComponentInfo(this);
  
        componentInfo && Object.keys(componentInfo.instances).forEach(function(k) {
          var info = componentInfo.instances[k];
          info.instance.teardown();
        });
      }
  
      function checkSerializable(type, data) {
        try {
          window.postMessage(data, '*');
        } catch(e) {
          console.log('unserializable data for event',type,':',data);
          throw new Error(
            ["The event", type, "on component", this.toString(), "was triggered with non-serializable data"].join(" ")
          );
        }
      }
  
      //common mixin allocates basic functionality - used by all component prototypes
      //callback context is bound to component
      function withBaseComponent() {
  
        // delegate trigger, bind and unbind to an element
        // if $element not supplied, use component's node
        // other arguments are passed on
        // event can be either a string specifying the type
        // of the event, or a hash specifying both the type
        // and a default function to be called.
        this.trigger = function() {
          var $element, type, data, event, defaultFn;
          var lastIndex = arguments.length - 1, lastArg = arguments[lastIndex];
  
          if (typeof lastArg != "string" && !(lastArg && lastArg.defaultBehavior)) {
            lastIndex--;
            data = lastArg;
          }
  
          if (lastIndex == 1) {
            $element = $(arguments[0]);
            event = arguments[1];
          } else {
            $element = this.$node;
            event = arguments[0];
          }
  
          if (event.defaultBehavior) {
            defaultFn = event.defaultBehavior;
            event = $.Event(event.type);
          }
  
          type = event.type || event;
  
          if (debug.enabled && window.postMessage) {
            checkSerializable.call(this, type, data);
          }
  
          if (typeof this.attr.eventData === 'object') {
            data = $.extend(true, {}, this.attr.eventData, data);
          }
  
          $element.trigger((event || type), data);
  
          if (defaultFn && !event.isDefaultPrevented()) {
            (this[defaultFn] || defaultFn).call(this);
          }
  
          return $element;
        };
  
        this.on = function() {
          var $element, type, callback, originalCb;
          var lastIndex = arguments.length - 1, origin = arguments[lastIndex];
  
          if (typeof origin == "object") {
            //delegate callback
            originalCb = utils.delegate(
              this.resolveDelegateRules(origin)
            );
          } else {
            originalCb = origin;
          }
  
          if (lastIndex == 2) {
            $element = $(arguments[0]);
            type = arguments[1];
          } else {
            $element = this.$node;
            type = arguments[0];
          }
  
          if (typeof originalCb != 'function' && typeof originalCb != 'object') {
            throw new Error("Unable to bind to '" + type + "' because the given callback is not a function or an object");
          }
  
          callback = originalCb.bind(this);
          callback.target = originalCb;
  
          // if the original callback is already branded by jQuery's guid, copy it to the context-bound version
          if (originalCb.guid) {
            callback.guid = originalCb.guid;
          }
  
          $element.on(type, callback);
  
          // get jquery's guid from our bound fn, so unbinding will work
          originalCb.guid = callback.guid;
  
          return callback;
        };
  
        this.off = function() {
          var $element, type, callback;
          var lastIndex = arguments.length - 1;
  
          if (typeof arguments[lastIndex] == "function") {
            callback = arguments[lastIndex];
            lastIndex -= 1;
          }
  
          if (lastIndex == 1) {
            $element = $(arguments[0]);
            type = arguments[1];
          } else {
            $element = this.$node;
            type = arguments[0];
          }
  
          return $element.off(type, callback);
        };
  
        this.resolveDelegateRules = function(ruleInfo) {
          var rules = {};
  
          Object.keys(ruleInfo).forEach(function(r) {
            if (!r in this.attr) {
              throw new Error('Component "' + this.toString() + '" wants to listen on "' + r + '" but no such attribute was defined.');
            }
            rules[this.attr[r]] = ruleInfo[r];
          }, this);
  
          return rules;
        };
  
        this.defaultAttrs = function(defaults) {
          utils.push(this.defaults, defaults, true) || (this.defaults = defaults);
        };
  
        this.select = function(attributeKey) {
          return this.$node.find(this.attr[attributeKey]);
        };
  
        this.initialize = $.noop;
        this.teardown = teardown;
      }
  
      function attachTo(selector/*, options args */) {
        // unpacking arguments by hand benchmarked faster
        var l = arguments.length;
        var args = new Array(l - 1);
        for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
  
        if (!selector) {
          throw new Error("Component needs to be attachTo'd a jQuery object, native node or selector string");
        }
  
        var options = utils.merge.apply(utils, args);
  
        $(selector).each(function(i, node) {
          var rawNode = node.jQuery ? node[0] : node;
          var componentInfo = registry.findComponentInfo(this)
          if (componentInfo && componentInfo.isAttachedTo(rawNode)) {
            //already attached
            return;
          }
  
          new this(node, options);
        }.bind(this));
      }
  
      // define the constructor for a custom component type
      // takes an unlimited number of mixin functions as arguments
      // typical api call with 3 mixins: define(timeline, withTweetCapability, withScrollCapability);
      function define(/*mixins*/) {
        // unpacking arguments by hand benchmarked faster
        var l = arguments.length;
        var mixins = new Array(l + 3); //add three for common mixins
        for (var i = 0; i < l; i++) mixins[i] = arguments[i];
  
        Component.toString = function() {
          var prettyPrintMixins = mixins.map(function(mixin) {
            if (mixin.name == null) {
              //function name property not supported by this browser, use regex
              var m = mixin.toString().match(functionNameRegEx);
              return (m && m[1]) ? m[1] : "";
            } else {
              return (mixin.name != "withBaseComponent") ? mixin.name : "";
            }
          }).filter(Boolean).join(', ');
          return prettyPrintMixins;
        };
  
        if (debug.enabled) {
          Component.describe = Component.toString();
        }
  
        //'options' is optional hash to be merged with 'defaults' in the component definition
        function Component(node, options) {
          options = options || {};
          this.identity = componentId++;
  
          if (!node) {
            throw new Error("Component needs a node");
          }
  
          if (node.jquery) {
            this.node = node[0];
            this.$node = node;
          } else {
            this.node = node;
            this.$node = $(node);
          }
  
          this.toString = Component.toString;
          if (debug.enabled) {
            this.describe = this.toString();
          }
  
          //merge defaults with supplied options
          //put options in attr.__proto__ to avoid merge overhead
          var attr = Object.create(options);
          for (var key in this.defaults) {
            if (!options.hasOwnProperty(key)) {
              attr[key] = this.defaults[key];
            }
          }
          this.attr = attr;
  
          Object.keys(this.defaults || {}).forEach(function(key) {
            if (this.defaults[key] === null && this.attr[key] === null) {
              throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.toString() + '".');
            }
          }, this);
  
          this.initialize.call(this, options);
        }
  
        Component.attachTo = attachTo;
        Component.teardownAll = teardownAll;
  
        // prepend common mixins to supplied list, then mixin all flavors
        if (debug.enabled) {
          mixins.unshift(withLogging);
        }
        mixins.unshift(withBaseComponent, advice.withAdvice, registry.withRegistration);
        compose.mixin(Component.prototype, mixins);
  
        return Component;
      }
  
      define.teardownAll = function() {
        registry.components.slice().forEach(function(c) {
          c.component.teardownAll();
        });
        registry.reset();
      };
  
      return define;
    }
  );
  