//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.)
  **/
  
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;
	}
} );


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 );
	},
	
	
	
	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,
	
	
	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;
			
			
			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) {

			
			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});
		}
		

	}
	
	
});
