//EVENT MIXINS

/**
  *  Event Mixins
  *  (c) 2006 Seth Dillingham <seth.dillingham@gmail.com>
  *
  *  This software is hereby released into the public domain. Do with it as
  *  you please, but with the understanding that it is provided "AS IS" and 
  *  without any warranty of any kind.
  *  
  *  (But I'd love to be told about where and how this code is being used.)
  **/
  
/**
  *  Description:
  *    add support (to any object or class) by mixing this class into your own
  *  
  *  Requires prototype.js
  *  
  *  Usage:
  *    To publish custom events:
  *      1. mix this class with your own via
  *         Object.extend( [your class or prototype], Event.Publisher )
  *      2. post events by calling
  *         this.dispatchEvent( [event name], [data for event] )
  *   
  *    To activate and deactivate the event-tracing feature, just call 
  *      this.toggleEventsTrace()
  **/

Event.Publisher = Class.create();
Object.extend( Event.Publisher, {
	_ls_event_targets: null,
	
	_event_source_id: null,
	
	_fl_trace_events: false,
	
	getEventSourceId: function() {
		if ( typeof this._event_source_id == 'function' )
			return this._event_source_id();
		else
			return this._event_source_id;
	},
	
	getEventTarget: function( event_name ) {
		if ( ! this._ls_event_targets )
			this._ls_event_targets = new Array();
		
		if ( ! this._ls_event_targets[ event_name ] )
			document.body.appendChild(
				this._ls_event_targets[ event_name ] = document.createElement( 'A' )
			);
		
		return this._ls_event_targets[ event_name ];
	},
	
	addEventListener: function( event_name, callback_func, capturing ) {
		var targ = this.getEventTarget( event_name );
		
		Event.observe( targ, 'click', callback_func, capturing );
		
		if ( this._fl_trace_events ) {
			var data =  {
				publisher: this.getEventSourceId(),
				event_name: event_name,
				listener: callback_func,
				capturing: capturing,
				event_source_proxy: targ
			};
			
			this.dispatchEvent( 'eventListenerAdded', data, true, true );
		}
	},
	
	removeEventListener: function( event_name, callback_func, capturing ) {
		var targ = this.getEventTarget( event_name );
		
		Event.stopObserving( targ, 'click', callback_func, capturing );
		
		if ( this._fl_trace_events ) {
			var data =  {
				publisher: this.getEventSourceId(),
				event_name: event_name,
				listener: callback_func,
				capturing: capturing,
				event_source_proxy: targ
			};
			
			this.dispatchEvent( 'eventListenerRemoved', data, true, true );
		}
	},
	
	dispatchEvent: function( event_name, data, can_bubble, cancelable ) {
		var targ = this.getEventTarget( event_name );
		var event_data = {
			event_name: event_name,
			event_target: this,
			data: data ? data : null
		};
		
		if ( ! can_bubble ) can_bubble = false;
		if ( ! cancelable ) cancelable = false;
		
		var event = Event.create( event_data, can_bubble, cancelable, true, targ );
		
		if ( this._fl_trace_events ) {
			if ( event_name.match( /event(?:ListenerAdded|ListenerRemoved|Dispatched|Received)/ ) )
				return;
			
			var data =  {
				publisher: this.getEventSourceId(),
				event_name: event_name,
				event_data: event_data,
				can_bubble: can_bubble,
				cancelable: cancelable,
				event_source_proxy: targ,
				result: event
			};
			
			this.dispatchEvent( 'eventDispatched', data, true, true );
		}
	},
	
	toggleEventsTrace: function() {
		var trace = Event.Tracer.findTracer();
		
		if ( ! trace || ! this._fl_trace_events ) {
			this._fl_trace_events = true;
			
			trace = Event.Tracer.startTrace();
			
			trace.registerPublisher( this );
		}
		else {
			this._fl_trace_events = false;
			
			if ( trace )
				trace.unregisterPublisher( this );
		}
		
		return this._fl_trace_events;
	},
	
	isEventsTraceActive: function() {
		return this._fl_trace_events;
	}
} );

/**
  *  MIX IN: Event.Listener
  *  
  *  Description:
  *    easily add support for receiving totally custom events
  *    (to any object or class) by mixing this class into
  *    your own
  *  
  *  Usage:
  *	   To receive custom events:
  *      1. mix this class with your own via
  *         Object.extend( [your class or prototype], EventListener )
  *      2. listen for events by calling (from your object)
  *         this.listen()
  *         (see params for this.listen, below)
  **/
Event.Listener = Class.create();
Object.extend( Event.Listener,
{
	_listens: null,
	
	getEventHandlerName: function( event_name ) {
		var onEvent_name = event_name.split( /[ _]/ ).join( '-' ).camelize();
		
		return "on" + onEvent_name.charAt( 0 ).toUpperCase() + onEvent_name.substr( 1 );
	},
	
	/**
	  *	 Params:
      *    event_source [object]:
      *      the object which will generate the events, and which implements (or
      *      mixes in) the Event.Publisher interface (we need addEventListener)
      *    event_name [string]:
      *      the name of the event for which your object will listen
      *    use_capture [boolean]:
      *      standard DOM Event API param
      *    onEvent_name [string]:
      *      the name of the method in your object which will be called when the
      *      event is received if you omit this param, listen will look for a
      *      function named with the CapitalizedCamelCased name of the event with
      *      "on" at the front. So, if the event is named "message_received",
      *      we'll look for a function named "onMessageReceived" You can override
      *      this behavior by overriding getEventHandlerName in your object.
	  **/
	listenForEvent: function( event_source, event_name, use_capture, onEvent_name ) {
		if ( ! onEvent_name )
			onEvent_name = this.getEventHandlerName( event_name );
		
		if ( ! this._listens ) this._listens = new Array();
		
		//added this in to allow for anonymous function handling of an event
		var eventHandler = this[onEvent_name];
		
		if(typeof(onEvent_name) == 'function') {
			eventHandler = onEvent_name;
		}
		
		var cb = eventHandler.bindAsEventListener( this );
		this._listens.push( [ event_source, event_name, use_capture, onEvent_name, cb ] )
		
		event_source.addEventListener( event_name, cb, use_capture );
	},
	
	stopListeningForEvent: function( event_source, event_name, use_capture, onEvent_name ) {
		if ( ! this._listens ) return false;
		
		if ( ! onEvent_name )
			onEvent_name = this.getEventHandlerName( event_name );
		
		var ix_item = -1;
		var ls = this._listens.detect( function( val, ix ) {
			if ( ( val[ 0 ] == event_source )
			  && ( val[ 1 ] == event_name )
			  && ( val[ 2 ] == use_capture )
			  && ( val[ 3 ] == onEvent_name ) ) {
				ix_item = ix;
				return true;
			}
		} );
		
		if ( ix_item >= 0 ) {
			this._listens.splice( ix_item, 1 );
			
			event_source.removeEventListener( event_name, ls[ 4 ], use_capture );
			
			return true;
		}
		
		return false;
	}
} );

/**
  *  Extensions to Prototype's Event object,
  *  for cleanly creating and dispatching custom events
  *  
  *  Called from Event.Publisher
  **/
Object.extend( Event,
{
	create: function( event_data, can_bubble, cancelable, fl_dispatch, target ) {
		var event;
		
		if ( document.createEvent ) {  // gecko, safari
			if ( ! can_bubble ) can_bubble = false;
			if ( ! cancelable ) cancelable = false;
			
			if ( /Konqueror|Safari|KHTML/.test( navigator.userAgent ) ) {
				event = document.createEvent( 'HTMLEvents' )
				
				event.initEvent( 'click', can_bubble, cancelable );
			}
			else {  // gecko uses MouseEvents
				event = document.createEvent( 'MouseEvents' )
				
				event.initMouseEvent( "click", can_bubble, cancelable,
				                      window, 0, 0, 0, 0, 0,
				                      false, false, false, false, 0, null );
			}
		}
		else {  // msie
			event = document.createEventObject();
			event.event_type = 'onclick';
		}
		
		event.event_data = event_data;
		
		if ( fl_dispatch )
			Event.dispatch( target, event );
		
		return event;
	},
	
	dispatch: function( target, event ) {
		if ( document.createEvent )
			return target.dispatchEvent( event );
		else
			return target.fireEvent( ( typeof( event.event_type ) != "undefined" ) ? event.event_type : 'onclick', event );
	}
} );




//END EVENT MIXINS


//BROWSER DETECT

if(typeof (AC)==="undefined"){AC={};}AC.Detector={getAgent:function(){return navigator.userAgent.toLowerCase();},isMac:function(M){var U=M||this.getAgent();return !!U.match(/mac/i);},isWin:function(M){var U=M||this.getAgent();return !!U.match(/win/i);},isWin2k:function(M){var U=M||this.getAgent();return this.isWin(U)&&(U.match(/nt\s*5/i));},isWinVista:function(M){var U=M||this.getAgent();return this.isWin(U)&&(U.match(/nt\s*6/i));},isWebKit:function(M){var U=M||this.getAgent();return !!U.match(/AppleWebKit/i);},isOpera:function(M){var U=M||this.getAgent();return !!U.match(/opera/i);},isIE:function(M){var U=M||this.getAgent();return !!U.match(/msie/i);},isIEStrict:function(M){var U=M||this.getAgent();return U.match(/msie/i)&&!this.isOpera(U);},isFirefox:function(M){var U=M||this.getAgent();return !!U.match(/firefox/i);},isiPhone:function(M){var U=M||this.getAgent();return this.isMobile(U);},isMobile:function(M){var U=M||this.getAgent();return this.isWebKit(U)&&U.match(/Mobile/i);},isiTunesOK:function(M){var U=M||this.getAgent();return this.isMac(U)||this.isWin2k(U);},isQTInstalled:function(){var U=false;if(navigator.plugins&&navigator.plugins.length){for(var M=0;M<navigator.plugins.length;M++){var g=navigator.plugins[M];if(g.name.indexOf("QuickTime")>-1){U=true;}}}else{qtObj=false;execScript("on error resume next: qtObj = IsObject(CreateObject(\"QuickTimeCheckObject.QuickTimeCheck.1\"))","VBScript");U=qtObj;}return U;},getQTVersion:function(){var U="0";if(navigator.plugins&&navigator.plugins.length){for(var g=0;g<navigator.plugins.length;g++){var S=navigator.plugins[g];var M=S.name.match(/quicktime\D*([\.\d]*)/i);if(M&&M[1]){U=M[1];}}}else{ieQTVersion=null;execScript("on error resume next: ieQTVersion = CreateObject(\"QuickTimeCheckObject.QuickTimeCheck.1\").QuickTimeVersion","VBScript");if(ieQTVersion){U=(ieQTVersion>>24).toString(16);}}return U;},isQTCompatible:function(g,j){function M(w,R){var i=parseInt(w[0],10);if(isNaN(i)){i=0;}var V=parseInt(R[0],10);if(isNaN(V)){V=0;}if(i===V){if(w.length>1){return M(w.slice(1),R.slice(1));}else{return true;}}else{if(i<V){return true;}else{return false;}}}var S=g.split(/\./);var U=j?j.split(/\./):this.getQTVersion().split(/\./);return M(S,U);},isValidQTAvailable:function(U){return this.isQTInstalled()&&this.isQTCompatible(U);}};



// END BROWSER DETECT CODE 


if (typeof(AC) == "undefined") { AC = {}; }

AC.Bureau = Class.create();
Object.extend(AC.Bureau.prototype, Event.Listener);
Object.extend(AC.Bureau.prototype, {
	
	drawers: null,
	container: null,
	
	triggerTimeout: null,
	
	initialize: function(container) {
		this.drawers = [];
		this.container = $(container);
	},
	
	addDrawer: function(newDrawer) {},
	
	getDrawerCount: function() {
		return this.drawers.length;
	},
	
	hasDrawers: function() {
		return (this.drawers.length > 0);
	},
	
	getFirstDrawer: function() {
		return this.drawers[0] || null;
	},
	
	getLastDrawer: function() {
		return this.drawers[this.drawers.length-1] || null;
	},
	
	scheduleTrigger: function(onFire, delay) {
		this.triggerTimeout = setTimeout(onFire, delay);
	},
	
	clearTrigger: function() {
		clearTimeout(this.triggerTimeout);
	}

});

AC.Drawer = Class.create();
Object.extend(AC.Drawer.prototype, Event.Publisher);
Object.extend(AC.Drawer.prototype, {
	
	bureau: null,
	
	contentElement: null,
	handle: null,
	indicator: null,
	
	isOpen: true,
	
	beforeOpen: null,
	afterOpen: null,
	
	beforeClose: null,
	afterClose: null,
	
	transitionDuration: 0.3,
	triggerDelay: 0,
	
	//TODO I'd love to not to have thid circular association where the drawers need to know about the bureau and vice versa
	//already in some of the newer code like sliderdrawers this dependency has been negated by the use of the event mixins
	//the bureau simply observes its drawers for any changes
	//might need to see if this is true for all drawer/bureau classes
	initialize: function(contentElement, handleElement, bureau, options) {
		
		this.contentElement = $(contentElement);
		this.handle = $(handleElement);
		this.bureau = bureau;
		
		var triggerEvent = 'click';
		
		if(options != null && typeof(options) != 'undefined') {
			this.beforeOpen = options.beforeOpen;
			this.afterOpen = options.afterOpen;
			this.beforeClose = options.beforeClose;
			this.afterClose = options.afterClose;
			
			//preserve defaults, but override as necessary
			//we're only allowing clicks to trigger things on the iphone
			if (typeof(options.triggerEvent) != 'undefined') {
				triggerEvent = options.triggerEvent;
			}
			
			if(typeof(options.triggerDelay) != 'undefined') {
				this.triggerDelay = options.triggerDelay;
			}
			
			if(typeof(options.transitionDuration) != 'undefined') {
				this.transitionDuration = options.transitionDuration;
			}
		}
		
		if (AC.Detector.isiPhone()) {
			this.transitionDuration = 0;
			triggerEvent = 'click';
		}
		
		Element.addClassName(this.contentElement, 'last');
		
		var fireTrigger = function(evt) {

			//TODO really really bad, base drawer class does not have an 
			//isVisible property, this is here as a hack for shingles which
			//are "open" even when you go to make them visible meaning
			//it would be impossible to even triggewr an open drawer if we
			//just checked for the open status
			
			//so the iphone will still follow links on open drawers if the 
			//handle happens to be a link
			if(AC.Detector.isiPhone() && (this.isOpen && (this.isVisible === true)) && this.handle.tagName.match(/a/i)) {
				return;
			}
			
			Event.stop(evt);
			
			if(this.triggerDelay > 0) {
				var onFire = this.trigger.bind(this);
				bureau.scheduleTrigger(onFire, this.triggerDelay);
			} else {
				this.trigger();
			}
		}
		
		Event.observe(this.handle, triggerEvent, fireTrigger.bind(this), false);
		Event.observe(this.handle, 'mouseout', bureau.clearTrigger.bind(bureau), false);
		
	},
	
	toggle: function() {},
	
	open: function() {},
	
	close: function() {}
	
});


AC.SlidingBureau = Class.create();
Object.extend(AC.SlidingBureau.prototype, AC.Bureau.prototype);
Object.extend(AC.SlidingBureau.prototype, {
	
	isLocked: false,
	
	addDrawer: function(newDrawer) {
		
		Element.addClassName(newDrawer.contentElement, 'last');
		Element.addClassName(newDrawer.handle, 'last');
		
		if(this.hasDrawers()) {
			
			var lastDrawer = this.getLastDrawer();
			
			lastDrawer.setNextDrawer(newDrawer);
			newDrawer.setPreviousDrawer(lastDrawer);
		} else {
			Element.addClassName(newDrawer.contentElement, 'first');
			Element.addClassName(newDrawer.handle, 'first');
		}
		
		this.listenForEvent(newDrawer, 'beforeOpen', false, function(evt) {
			var drawer = evt.event_data.data;
			this.open(drawer);
		});
		
		this.listenForEvent(newDrawer, 'afterOpen', false, function(evt) {
			var drawer = evt.event_data.data;
			this.acknowledgeOpened(drawer);
		});
		
		this.listenForEvent(newDrawer, 'beforeClose', false, function(evt) {
			var drawer = evt.event_data.data;
			this.close(drawer);
		});
		
		this.listenForEvent(newDrawer, 'afterClose', false, function(evt) {
			var drawer = evt.event_data.data;
			this.acknowledgeClosed(drawer);
		});
		
		//TODO may want to change how this is done but we need a way to 
		//keep one drawer open initially
		if (!Element.hasClassName(newDrawer.contentElement, 'open')) {
			newDrawer.initiateClose();
		} else {
			this.currentDrawer = newDrawer;
		}
		
		this.drawers.push(newDrawer);
	},
	
	open: function(drawer) {
		
		if(this.isLocked){
			return;
		}
		
		this.isLocked = true;

		//lock size of container to prevent shifting, but only if the 
		//implementation of the container is expecting that
		
		//TODO I'd love to do this with an Effect.Parallel of the open and 
		//close without all this drawer wedging during the animation but
		//I like others had issues in various browsers with that approach
		//http://wiki.script.aculo.us/scriptaculous/show/accordion+feature
		if (Element.getStyle(this.container, 'position') == 'relative') {
			
			var dimensions = Element.getDimensions(this.container);
			Element.setStyle(this.container, {height: dimensions.height + "px"});
		
			this.wedgeDrawersAfter(drawer);
			
			//we want to preserve the minheight specified on the drawers
			//because in these cases that's what is specified to size the 
			//drawers to whatever particular design somebody cooked up
			//but we can't have a minimum height specified during the 
			//animation or the animation never actually appears
			var minHeight = Element.getStyle(drawer.contentElement, 'min-height');
			
			if (minHeight) {
				Element.setStyle(drawer.contentElement, {
					'min-height': '0px', //clear the minimum height restriction
					height: minHeight}) //set the desired height of the element
			}
		}
		
		
		if (this.currentDrawer) {
			this.currentDrawer.initiateClose();
		}
		
		drawer.open(minHeight);
	},
	
	acknowledgeOpened: function(drawer) {
		this.currentDrawer = drawer;
		
		if (Element.getStyle(this.container, 'position') == 'relative') {
			if (!AC.Detector.isIEStrict()) {
				Element.setStyle(this.container, {height: "auto"});
			}
			this.unwedgeDrawers();
		}
		
		this.isLocked = false;
	},
	
	close: function(drawer) {
		
		if (Element.getStyle(this.container, 'position') == 'relative') {
				var minHeight = Element.getStyle(drawer.contentElement, 'min-height');
				
				if(minHeight) {
					Element.setStyle(drawer.contentElement, {
						height: minHeight, //lock in the starting height
						'min-height': '0px'}); //remove minimum height restriction
				}
		}
		
		drawer.close(minHeight);
	},
	
	acknowledgeClosed: function(drawer) {
		if(drawer == this.currentDrawer) {
			this.currentDrawer = null;
		}
	},
	
	wedgeDrawersAfter: function(drawerBeingOpened) {

		var wedgeDrawer = function(drawer, offset) {
			Element.setStyle(drawer.handle, {
				position: 'absolute',
				bottom: offset + 'px'})
		}
		
		var drawer = this.getLastDrawer();
		var offset = 0;
		
		while (drawer!= this.currentDrawer && drawer != drawerBeingOpened) {
			wedgeDrawer(drawer, offset);
			offset += drawer.handle.getHeight();
			drawer = drawer.previousDrawer;
		}
		
	},
	
	unwedgeDrawers: function() {
		for (var i = this.drawers.length - 1; i >= 0; i--){
			Element.setStyle(this.drawers[i].handle, {
				position: 'static'})
		};
	}

	
});

AC.SlidingDrawer = Class.create();
Object.extend(AC.SlidingDrawer.prototype, AC.Drawer.prototype);
Object.extend(AC.SlidingDrawer.prototype, {
	
	isOpen: true,
	isTransitioning: false,
	
	setNextDrawer: function(drawer) {
		this.nextDrawer = drawer;
		Element.removeClassName(this.contentElement, 'last');
		Element.removeClassName(this.handle, 'last');
	},
	
	setPreviousDrawer: function(drawer) {
		this.previousDrawer = drawer;
	},
	
	trigger: function() {
		this.toggle();
	},
	
	toggle: function() {
		
		if(!this.isOpen) {
			this.initiateOpen();
		}
		
	},
	
	initiateOpen: function() {
		
		if (this.isTransitioning || this.isOpen) {
			return;
		}
		
		this.dispatchEvent('beforeOpen', this);
		
	},
	
	open: function(minHeight) {
		
		this.isTransitioning = true;
		
		//need to do this before effect starts so content is visible
		Element.addClassName(this.contentElement, 'open');
		Element.addClassName(this.handle, 'open');
		
		var afterFinish = function() {
			this.isOpen = true;
			if (minHeight) {
				Element.setStyle(this.contentElement, {'min-height': minHeight});
				if (!AC.Detector.isIEStrict()) {
					Element.setStyle(this.contentElement, {'height': 'auto'});
				}
			}
			this.dispatchEvent('afterOpen', this);
			this.isTransitioning = false;
		}.bind(this);
		
		if (AC.Detector.isiPhone()) {
			this.contentElement.show();
			afterFinish();
		} else {
			new Effect.BlindDown(this.contentElement, {
				duration: this.transitionDuration,
				afterFinish: afterFinish});
		}
	},
	
	initiateClose: function(force) {
		
		if (this.isTransitioning || !this.isOpen) {
			return;
		}
		
		this.dispatchEvent('beforeClose', this);
	},
	
	close: function(minHeight) {
		
		this.isTransitioning = true;
		
		var afterFinish = function() {
			this.isOpen = false;
			Element.removeClassName(this.contentElement, 'open');
			Element.removeClassName(this.handle, 'open');
			if(minHeight) {
				Element.setStyle(this.contentElement, {'min-height': minHeight});
				if (!AC.Detector.isIEStrict()) {
					Element.setStyle(this.contentElement, {'height': 'auto'});
				}
			}
			this.dispatchEvent('afterClose', this);
			this.isTransitioning = false;
		}.bind(this);
		
		if(AC.Detector.isiPhone()) {
			this.contentElement.hide();
			afterFinish();
		} else {
			new Effect.BlindUp(this.contentElement, {
				duration: this.transitionDuration,
				afterFinish:  afterFinish});
		}
		

	}
	
	
});

/**
 * Overlapping Shingles
 * Many sections open, only one visible at a time
 */
AC.ShingleBureau = Class.create();
Object.extend(Object.extend(AC.ShingleBureau.prototype, AC.Bureau.prototype), {
	
	drawerDuration: 0.5,
	
	addDrawer: function(newDrawer) {
		
		//establish relationships between drawers
		if(this.hasDrawers()) {
			
			var lastDrawer = this.getLastDrawer();
			
			lastDrawer.setNextDrawer(newDrawer);
			newDrawer.setPreviousDrawer(lastDrawer);
			newDrawer.closedOffset = lastDrawer.closedOffset + lastDrawer.getHandleHeight() - 10;
		} else {
			Element.addClassName(newDrawer.contentElement, 'first');
			newDrawer.closedOffset = 0 - newDrawer.getHeight() + newDrawer.getHandleHeight() - 10;
			newDrawer.indicateVisible();
		}
		
		//add drawer to the collection
		this.drawers.push(newDrawer);
	},
	
	getWidth: function() {
		return Element.getWidth(this.container);
	},
	
	getHeight: function() {
		return Element.getHeight(this.container);
	},
	
	moveDrawer: function(drawer, x, y) {
		new Effect.Move(drawer, {
			x: x,
			y: y, 
			mode: 'absolute', 
			transition: Effect.Transitions.sinoidal,
			duration: this.drawerDuration});
	}
	
});

AC.ShingleDrawer = Class.create();
Object.extend(Object.extend(AC.ShingleDrawer.prototype, AC.Drawer.prototype), {
	
	openedOffset: 0,
	closedOffset: 0,
	
	previousDrawer: null,
	nextDrawer: null,
	
	isVisible: false,
	
	trigger: function() {
		if(!this.isVisible) {
			this.open(true);
			this.indicateVisible();
		}
	},
	
	toggle: function() {
		
		if(!this.isOpen) {
			this.open();
			this.indicateVisible();
		} else {
			this.close();
		}
		
	},
	
	open: function(force) {
		
		if (this.isOpen && !force) {
			return;
		}
		
		if(this.previousDrawer !== null) {
			this.previousDrawer.close();
			this.previousDrawer.indicateObscured();
		}
		
		if(this.nextDrawer !== null) {
			this.nextDrawer.open();
			this.nextDrawer.indicateObscured();
		}
		
		this.indicateVisible();
		this.isOpen = true;
		
		this.bureau.moveDrawer(this.contentElement, 0, this.openedOffset);
	},
	
	close: function(force) {
		
		if (!this.isOpen) {
			return;
		}
		
		if (this == this.bureau.getLastDrawer()) {
			return;
		}
		
		if(this.previousDrawer !== null) {
			this.previousDrawer.close();
		}
		
		this.bureau.moveDrawer(this.contentElement, 0, this.closedOffset);
		this.indicateObscured();
		this.isOpen = false;
		
	},
	
	setPreviousDrawer: function(drawer) {
		this.previousDrawer = drawer;
		
		this.indicateObscured();
		
		this.openedOffset = this.previousDrawer.openedOffset + this.previousDrawer.getHandleHeight() - 10;
		Element.setStyle(this.contentElement, {top: this.openedOffset + "px"});
	},
	
	setNextDrawer: function(drawer) {
		this.nextDrawer = drawer;
		
		Element.removeClassName(this.contentElement, 'last');
		
		if(this.previousDrawer != null) {
			this.previousDrawer.setNextDrawer(this);
		}
		
		//ensure the previous drawer remains on top, part of the single effect
		zIndex = parseInt(Element.getStyle(this.contentElement, 'zIndex'));
		Element.setStyle(this.contentElement, {'zIndex': zIndex + 1});
	},
	
	indicateObscured: function() {
		Element.addClassName(this.contentElement, 'obscured');
		this.isVisible = false;
	},
	
	indicateVisible: function() {
		this.isVisible = true;
		Element.removeClassName(this.contentElement, 'obscured');
	},
	
	getHandleWidth: function() {
		return Element.getWidth(this.handle);
	},
	
	getHandleHeight: function() {
		return Element.getHeight(this.handle);
	},
	
	getWidth: function() {
		return Element.getWidth(this.contentElement);
	},
	
	getHeight: function() {
		return Element.getHeight(this.contentElement);
	}
	
	
});


/**
 * Section Bureau
 * One section open and visible at a time
 */
AC.SectionBureau = Class.create();
Object.extend(AC.SectionBureau.prototype, AC.Bureau.prototype);
Object.extend(AC.SectionBureau.prototype, {
	
	currentDrawer: null,
	locked: false,
	
	addDrawer: function(newDrawer) {
		this.drawers.push(newDrawer);
		Element.addClassName(newDrawer.handle, 'obscured');
		Element.hide(newDrawer.contentElement);
	},

	openingDrawer: function(drawer) {
		if(this.currentDrawer != null) {
			this.currentDrawer.close();
		}
		
		this.currentDrawer = drawer;
	}
	
});

AC.SectionDrawer = Class.create();
Object.extend(AC.SectionDrawer.prototype, AC.Drawer.prototype);
Object.extend(AC.SectionDrawer.prototype, {
	
	isOpen: false,
	
	trigger: function() {
		this.toggle();
	},
	
	toggle: function() {
		
		if(!this.isOpen) {
			this.open();
		}
		
	},
	
	open: function() {
		
		if(this.bureau.locked) {
			return;
		}
		
		var afterTransition = function() {
			Element.show(this.contentElement);
		}.bind(this);
		//given an afterOpen callback, we need to stall in the very likely 
		//scenario that we can't use the bureau again until the callback
		//is completely finished. Onus is on the delegate to report finished
		
		//but we also don't lock up the bureau unless we need to
		
		if(typeof(this.afterOpen) == 'function') {
			this.bureau.locked = true;
			afterTransition = this.afterOpen.bind(this);
		}
		
		this.bureau.openingDrawer(this);
		
		if(typeof(this.beforeOpen) == 'function') {
			this.beforeOpen();
		}
		
		this.isOpen = true;
		Element.removeClassName(this.handle, 'obscured');
		
		new Effect.Appear(this.contentElement, {
			afterFinish: afterTransition,
			duration: this.transitionDuration,
			queue: {scope: 'sectionalscope'}});
		

	},
	
	close: function() {
		
		if(typeof(this.beforeClose) == 'function') {
			this.beforeClose();
		}
		
		this.isOpen = false;
		Element.addClassName(this.handle, 'obscured');
		
		
		var afterTransition = function() {
			if(typeof(this.afterClose) == 'function') {
				this.afterClose();
			}
			
		}.bind(this)
		
		new Effect.Fade(this.contentElement, {
			afterFinish: afterTransition,
			duration: this.transitionDuration,
			queue: {scope: 'sectionalscope'}});
		
	},
	
	reportFinishedOpening: function() {
		this.bureau.locked = false;
	}
	
});

