text: /**
   * A layer encapsulate a bunch of containers and wires
   * @class Layer
   * @namespace WireIt
   * @constructor
   * @param {Object}   options   Configuration object (see the properties)
   */
  WireIt.Layer = function(options) {
     
     this.setOptions(options);
     
     /**
      * List of all the WireIt.Container (or subclass) instances in this layer
      * @property containers
      * @type {Array}
      */
     this.containers = [];
     
     /**
      * List of all the WireIt.Wire (or subclass) instances in this layer
      * @property wires
      * @type {Array}
      */
     this.wires = [];
     
     /**
      * Layer DOM element
      * @property el
      * @type {HTMLElement}
      */
     this.el = null;
  
  	/**
      * Event that is fired when the layer has been changed
      * You can register this event with myTerminal.eventChanged.subscribe(function(e,params) { }, scope);
      * @event eventChanged
      */
     this.eventChanged = new YAHOO.util.CustomEvent("eventChanged");
     
     /**
      * Event that is fired when a wire is added
      * You can register this event with myTerminal.eventAddWire.subscribe(function(e,params) { var wire=params[0];}, scope);
      * @event eventAddWire
      */
     this.eventAddWire = new YAHOO.util.CustomEvent("eventAddWire");
     
     /**
      * Event that is fired when a wire is removed
      * You can register this event with myTerminal.eventRemoveWire.subscribe(function(e,params) { var wire=params[0];}, scope);
      * @event eventRemoveWire
      */
     this.eventRemoveWire = new YAHOO.util.CustomEvent("eventRemoveWire");
     
     /**
      * Event that is fired when a container is added
      * You can register this event with myTerminal.eventAddContainer.subscribe(function(e,params) { var container=params[0];}, scope);
      * @event eventAddContainer
      */
     this.eventAddContainer = new YAHOO.util.CustomEvent("eventAddContainer");
     
     /**
      * Event that is fired when a container is removed
      * You can register this event with myTerminal.eventRemoveContainer.subscribe(function(e,params) { var container=params[0];}, scope);
      * @event eventRemoveContainer
      */
     this.eventRemoveContainer = new YAHOO.util.CustomEvent("eventRemoveContainer");
     
     /**
      * Event that is fired when a container has been moved
      * You can register this event with myTerminal.eventContainerDragged.subscribe(function(e,params) { var container=params[0];}, scope);
      * @event eventContainerDragged
      */
     this.eventContainerDragged = new YAHOO.util.CustomEvent("eventContainerDragged");
     
     /**
      * Event that is fired when a container has been resized
      * You can register this event with myTerminal.eventContainerResized.subscribe(function(e,params) { var container=params[0];}, scope);
      * @event eventContainerResized
      */
     this.eventContainerResized = new YAHOO.util.CustomEvent("eventContainerResized");
     
     this.render();
     
     this.initContainers();
     
     this.initWires();
     
     if(this.options.layerMap) { 
        new WireIt.LayerMap(this, this.options.layerMapOptions);
     }
     
  };
  
  WireIt.Layer.prototype = {
  
     /**
      * @method setOptions
      */
     setOptions: function(options) {
        /**
         * Configuration object of the layer
         * <ul>
         *   <li>className: CSS class name for the layer element (default 'WireIt-Layer')</li>
         *   <li>parentEl: DOM element that schould contain the layer (default document.body)</li>
         *   <li>containers: array of container configuration objects</li>  
         *   <li>wires: array of wire configuration objects</li>
         *   <li>layerMap: boolean</li>
         *   <li>layerMapOptions: layer map options</li>
         * </ul>
         * @property options
         */
        this.options = {};
        this.options.className = options.className || 'WireIt-Layer';
        this.options.parentEl = options.parentEl || document.body;
        this.options.containers = options.containers || [];
        this.options.wires = options.wires || [];
        this.options.layerMap = YAHOO.lang.isUndefined(options.layerMap) ? false : options.layerMap;
        this.options.layerMapOptions = options.layerMapOptions;
        this.options.enableMouseEvents = YAHOO.lang.isUndefined(options.enableMouseEvents) ? true : options.enableMouseEvents;
     },
  
     /**
      * Create the dom of the layer and insert it into the parent element
      * @method render
      */
     render: function() {
     
        this.el = WireIt.cn('div', {className: this.options.className} );
     
        this.options.parentEl.appendChild(this.el);
     },
  
  
     /**
      * Create all the containers passed as options
      * @method initContainers
      */
     initContainers: function() {
        for(var i = 0 ; i < this.options.containers.length ; i++) {
           this.addContainer(this.options.containers[i]);
        } 
     },
  
     /**
      * Create all the wires passed in the config
      * @method initWires
      */
     initWires: function() {
        for(var i = 0 ; i < this.options.wires.length ; i++) {
           this.addWire(this.options.wires[i]);
        }
     },
  
     /**
      * Instanciate a wire given its "xtype" (default to WireIt.Wire)
      * @method addWire
      * @param {Object} wireConfig  Wire configuration object (see WireIt.Wire class for details)
      * @return {WireIt.Wire} Wire instance build from the xtype
      */
     addWire: function(wireConfig) {
        var type = eval(wireConfig.xtype || "WireIt.Wire");
     
        var src = wireConfig.src;
        var tgt = wireConfig.tgt;
     
        var terminal1 = this.containers[src.moduleId].getTerminal(src.terminal);
        var terminal2 = this.containers[tgt.moduleId].getTerminal(tgt.terminal);
        var wire = new type( terminal1, terminal2, this.el, wireConfig);
        wire.redraw();
     
        return wire;
     },
  
     /**
      * Instanciate a container given its "xtype": WireIt.Container (default) or a subclass of it.
      * @method addContainer
      * @param {Object} containerConfig  Container configuration object (see WireIt.Container class for details)
      * @return {WireIt.Container} Container instance build from the xtype
      */
     addContainer: function(containerConfig) {
     
        var type = eval('('+(containerConfig.xtype || "WireIt.Container")+')');
        if(!YAHOO.lang.isFunction(type)) {
           throw new Error("WireIt layer unable to add container: xtype '"+containerConfig.xtype+"' not found");
        }
        var container = new type(containerConfig, this);
     
        this.containers.push( container );
     
        // Event listeners
        container.eventAddWire.subscribe(this.onAddWire, this, true);
        container.eventRemoveWire.subscribe(this.onRemoveWire, this, true);
     
        if(container.ddResize) {
           container.ddResize.on('endDragEvent', function() {
              this.eventContainerResized.fire(container);
  				this.eventChanged.fire(this);
           }, this, true);
        }
        if(container.dd) {
           container.dd.on('endDragEvent', function() {
              this.eventContainerDragged.fire(container);
  				this.eventChanged.fire(this);
           }, this, true);
        }
     
        this.eventAddContainer.fire(container);
  
  		this.eventChanged.fire(this);
     
        return container;
     },
  
     /**
      * Remove a container
      * @method removeContainer
      * @param {WireIt.Container} container Container instance to remove
      */
     removeContainer: function(container) {
        var index = WireIt.indexOf(container, this.containers);
        if( index != -1 ) {
           container.remove();
           this.containers[index] = null;
           this.containers = WireIt.compact(this.containers);
        
           this.eventRemoveContainer.fire(container);
  
  			this.eventChanged.fire(this);
        }
     },
  
     /**
      * Update the wire list when any of the containers fired the eventAddWire
      * @method onAddWire
      * @param {Event} event The eventAddWire event fired by the container
      * @param {Array} args This array contains a single element args[0] which is the added Wire instance
      */
     onAddWire: function(event, args) {
        var wire = args[0];
        // add the wire to the list if it isn't in
        if( WireIt.indexOf(wire, this.wires) == -1 ) {
           this.wires.push(wire);
           
           if(this.options.enableMouseEvents) {
              YAHOO.util.Event.addListener(wire.element, "mousemove", this.onWireMouseMove, this, true);
              YAHOO.util.Event.addListener(wire.element, "click", this.onWireClick, this, true);
           }
           
           // Re-Fire an event at the layer level
           this.eventAddWire.fire(wire);
  
  			// Fire the layer changed event
  			this.eventChanged.fire(this);
        }
     },
  
     /**
      * Update the wire list when a wire is removed
      * @method onRemoveWire
      * @param {Event} event The eventRemoveWire event fired by the container
      * @param {Array} args This array contains a single element args[0] which is the removed Wire instance
      */
     onRemoveWire: function(event, args) {
        var wire = args[0];
        var index = WireIt.indexOf(wire, this.wires);
        if( index != -1 ) {
           this.wires[index] = null;
           this.wires = WireIt.compact(this.wires);
           this.eventRemoveWire.fire(wire);
  			this.eventChanged.fire(this);
        }
     },
  
  
     /**
      * Remove all the containers in this layer (and the associated terminals and wires)
      * @method clear
      */
     clear: function() {
  		while(this.containers.length > 0) {
           this.removeContainer(this.containers[0]);
        }
     },
  
     /**
      * Alias for clear
      * @deprecated
      * @method removeAllContainers
      */
     removeAllContainers: function() {
        this.clear();
     },
  
  
     /**
      * Return an object that represent the state of the layer including the containers and the wires
      * @method getWiring
      * @return {Obj} layer configuration
      */
     getWiring: function() {
     
        var i;
        var obj = {containers: [], wires: []};
     
        for( i = 0 ; i < this.containers.length ; i++) {
           obj.containers.push( this.containers[i].getConfig() );
        }
     
        for( i = 0 ; i < this.wires.length ; i++) {
           var wire = this.wires[i];
        
           var wireObj = { 
              src: {moduleId: WireIt.indexOf(wire.terminal1.container, this.containers), terminal: wire.terminal1.name }, 
              tgt: {moduleId: WireIt.indexOf(wire.terminal2.container, this.containers), terminal: wire.terminal2.name }
           };
           obj.wires.push(wireObj);
        }
     
        return obj;
     },
  
     /**
      * Load a layer configuration object
      * @method setWiring
      * @param {Object} wiring layer configuration
      */
     setWiring: function(wiring) {
        this.clear();
        
        if(YAHOO.lang.isArray(wiring.containers)) {
           for(var i = 0 ; i < wiring.containers.length ; i++) {
              this.addContainer(wiring.containers[i]);
           }
        }
        if(YAHOO.lang.isArray(wiring.wires)) {
           for(var i = 0 ; i < wiring.wires.length ; i++) {
              this.addWire(wiring.wires[i]);
           }
         }
     },
     
     /**
      * Returns a position relative to the layer from a mouse event
      * @method _getMouseEvtPos
      * @param {Event} e Mouse event
      * @return {Array} position
      */
     _getMouseEvtPos: function(e) {
     	var tgt = YAHOO.util.Event.getTarget(e);
     	var tgtPos = [tgt.offsetLeft, tgt.offsetTop];
     	return [tgtPos[0]+e.layerX, tgtPos[1]+e.layerY];
     },
  
     /**
      * Handles click on any wire canvas
      * Note: we treat mouse events globally so that wires behind others can still receive the events
      * @method onWireClick
      * @param {Event} e Mouse click event
      */
     onWireClick: function(e) {
        var p = this._getMouseEvtPos(e);
     	var lx = p[0], ly = p[1], n = this.wires.length, w;
     	for(var i = 0 ; i < n ; i++) {
     	   w = this.wires[i];
        	var elx = w.element.offsetLeft, ely = w.element.offsetTop;
        	// Check if the mouse is within the canvas boundaries
     	   if( lx >= elx && lx < elx+w.element.width && ly >= ely && ly < ely+w.element.height ) {
     	      var rx = lx-elx, ry = ly-ely; // relative to the canvas
     			w.onClick(rx,ry);
     	   }
     	}
     },
  
     /**
      * Handles mousemove events on any wire canvas
      * Note: we treat mouse events globally so that wires behind others can still receive the events
      * @method onWireMouseMove
      * @param {Event} e Mouse click event
      */
     onWireMouseMove: function(e) {
        var p = this._getMouseEvtPos(e);
     	var lx = p[0], ly = p[1], n = this.wires.length, w;
     	for(var i = 0 ; i < n ; i++) {
     	   w = this.wires[i];
        	var elx = w.element.offsetLeft, ely = w.element.offsetTop;
        	// Check if the mouse is within the canvas boundaries
     	   if( lx >= elx && lx < elx+w.element.width && ly >= ely && ly < ely+w.element.height ) {
     	      var rx = lx-elx, ry = ly-ely; // relative to the canvas
     			w.onMouseMove(rx,ry);
     	   }
     	}
     },
     
     
     /**
      * Layer explosing animation
      * @method clearExplode
      */
     clearExplode: function(callback, bind) {
  
        var center = [ Math.floor(YAHOO.util.Dom.getViewportWidth()/2),
     		            Math.floor(YAHOO.util.Dom.getViewportHeight()/2)];
        var R = 1.2*Math.sqrt( Math.pow(center[0],2)+Math.pow(center[1],2));
  
        for(var i = 0 ; i < this.containers.length ; i++) {
            var left = parseInt(dbWire.layer.containers[i].el.style.left.substr(0,dbWire.layer.containers[i].el.style.left.length-2),10);
     	    var top = parseInt(dbWire.layer.containers[i].el.style.top.substr(0,dbWire.layer.containers[i].el.style.top.length-2),10);
  
     	    var d = Math.sqrt( Math.pow(left-center[0],2)+Math.pow(top-center[1],2) );
  
     	    var u = [ (left-center[0])/d, (top-center[1])/d];
     	    YAHOO.util.Dom.setStyle(this.containers[i].el, "opacity", "0.8");
  
     	    var myAnim = new WireIt.util.Anim(this.containers[i].terminals, this.containers[i].el, {
                left: { to: center[0]+R*u[0] },
                top: { to: center[1]+R*u[1] },
     	        opacity: { to: 0, by: 0.05},
     	        duration: 3
            });
            if(i == this.containers.length-1) {
               myAnim.onComplete.subscribe(function() { this.clear(); callback.call(bind);}, this, true); 
            }
     	    myAnim.animate();
        }
  
     }
     
  
  };
  