var moduleManager = function() {
	
	var _timer;
	var _timerId;
	var _isRunning = false;
	var _autoHeights = [];
	var _jsModules = {};
	var _frameParams = "?daffys_frame=1&module=1";
	
	var _moduleSpeed = 500;
	var _moduleEase = null;//"easeInOutQuad";
	
	var events = {OPEN: "open", CLOSE: "close"};
	var _methods = {OPEN: "onModuleOpen", CLOSE: "onModuleClose"};
	var _listeners = {};
	var _imageHeights = {};
	
	function openModule( $id )
	{
		if ( !$id ){ return; }
		if ( !isModuleLoaded( $id ) )
		{
			loadModule( $id );
			return;
		}
		if ( !isModuleOpen($id ) )
		{
			jsModule = _getJSModule($id);
			jsModule.onIntroStart();
			
			module = _getModule($id);
			container = _getContainer($id);
			$(module).addClass("open");
			
			// http://cdmckay.org/blog/2010/03/01/the-jquery-animate-step-callback-function/
			
			$(container).animate( {height: _getModuleHeight($id)}, 
				{
					duration:_moduleSpeed,
					ease: _moduleEase, 
					complete: jsModule.onIntroComplete,
					step: function( now, fx ) {
					//	_onModuleAnimateStep( $id );
					} 
				});
			
			//_adjustImages();
			_adjustImageSide2( _getModuleSide( $id ), true );
			_dispatchEvent( events.OPEN, [$id] );
		}
		
	}
	
	function closeModule( $id )
	{
		module = _getModule($id);
		container = _getContainer($id);
		$(module).removeClass("open");
		
		stopAutoHeight( $id );
		
		jsModule = _getJSModule($id);
		jsModule.onOutroStart();
		
		
		$(container).animate( {height: 0}, 
			{
				duration:_moduleSpeed,
				ease: _moduleEase, 
				complete: jsModule.onOutroComplete,
				step: function( now, fx ) {
				//	_onModuleAnimateStep( $id, now );
				} 
			});	
		
		_adjustImageSide2( _getModuleSide( $id ), true );
		//_adjustImages();
		_dispatchEvent( events.CLOSE, [$id] );
	}
	
	function toggleModule( $id )
	{
		isModuleOpen( $id ) ? closeModule( $id ) : openModule( $id );	
	}
	
	function isModuleOpen( $id )
	{
		return $(_getModule($id)).hasClass("open");
	}
	
	function isModuleLoading( $id )
	{
		container = _getContainer( $id );
		frame = $(container).find("iframe");
		return frame.length > 0 && !isModuleLoaded($id);
	}
	function isModuleLoaded( $id )
	{
		 return _jsModules && _jsModules[$id];
	}
	
	function loadModule( $id )
	{
		if ( isModuleLoading($id) ) {
			return;
		}
		container = _getContainer( $id );
		module = _getModule($id);
		
		$(module).addClass("loading");
		
		$(container).append( _createIFrame() );
		frame = $(container).find("iframe");
		$(frame).src( 
			_getModuleHref( module ),
			function()
			{
					// frame loaded
			}
		);
	}
	
	/* UTILITY METHODS
	----------------------------------------- */
	function _getIFrame( $id )
	{
		return $(_getContainer($id)).find("iframe");
	}
	function _getContainer( $id )
	{
		return $(_getModule($id)).find(".load-container");
	}
	function _getModule( $id )
	{
		return $(".module#module-"+$id); 
	}
	/* #module-{id} */
	function _parseId( $str )
	{
		return $str.split("-")[1];
	}
	
	function _createIFrame()
	{
		return '<iframe border="0" frameborder="0" marginheight="0" marginwidth="0" scrolling="no"/>';
	}
	function _getModuleHref( $module )
	{
		return $($module).find( "a").attr("href") + _frameParams;
	}
	/* returns which side of screen module is on (left/right) */
	function _getModuleSide($id)
	{
		holder = $(_getModule($id)).closest(".module-holder");
		return $(holder).hasClass("right") ? "right" : "left";
	}
	
	function _parseHeightInt( $str )
	{
		if ( $str==undefined ) {
			return 0;
		}
		if ( typeof $str == "number" ) {
			return $str;
		}
		return Number($str.split("px")[0]);
	}
	
	/* HEIGHT GETTER/SETTERS
	----------------------------------------- */
	// updates iframe height	
	function _setIframeHeight( $id, $h )
	{
		$(_getIFrame($id)).css("height", $h + "px");
	}

	// returns iframe height
	function _getIframeHeight( $id )
	{
		return $(_getIFrame($id)).css("height");
	}	
	// updates container height
	function _setContainerHeight( $id, $h )
	{
		$(_getContainer($id)).css("height", $h + "px");
	}
	
	// returns height of module 
	// requests height information from module object in iframe,
	// does not read directly from DOM;
	function _getModuleHeight( $id )
	{
		var frame = $(_getIFrame($id))[0];
		var win = frame.contentWindow;// frame.contentDocument || frame.contentWindow.document;
      	
		h = win.module.getHeight();
		return h;
	}
	
	// sets both iframe and container height (if open)
	function _setModuleHeight( $id, $h )
	{
		if ( !$h ) {
			$h = _getModuleHeight( $id );
		}
		_setIframeHeight( $id, $h );
		if ( isModuleOpen($id) )
		{
			_setContainerHeight( $id, $h );
			_adjustImageSide2( _getModuleSide($id), false );
		}
	}
	function _onModuleAnimateStep( $id )
	{
		try
		{
			_adjustImageSide2( _getModuleSide($id) );
		}
		 catch ( $e )
		{
			//trace( $e );
		}
	}
	/* IMAGE ADJUSTMENT
	----------------------------------------- */
	function _adjustImages()
	{
		var left = $(".module-holder.left");
		var right = $(".module-holder.right");
		
		//_adjustImageSide( left );
		//_adjustImageSide( right );
		
		//_adjustImageSide2( "left" );
		//_adjustImageSide2( "right" );
	}
	function _adjustImageSide2( side, $animate )
	{
		var holder = $(".module-holder."+side );
		var imgHolder = $(holder).find(".image-holder");
		var img = $(imgHolder).find(".image");
		var imgHeight = !_imageHeights[side] ? $(img).height() : _imageHeights[side];
		_imageHeights[side] = imgHeight;
		var above = false;
		var arr = $(holder).find(".module");
		var totalH = 0;
		
		for ( var i=0; i < arr.length; i++ )
		{
			var mod = arr[i];
			
			var id = _parseId( $(mod).attr("id") );
			var index = $(mod).parent().index();
			
			// get target height of module
			//var h = _getIframeHeight( id );
			//h = _parseHeightInt(h);
			var h;
			if ( $animate )
			{
				h = isModuleOpen(id ) ? _getIframeHeight(id) : 0;
				if ( h ) {
					h = _parseHeightInt(h);
				}
			}
			 else
			{
				h = _getContainer( id ).height();
			}
		//	var h = $animate ? _getIframeHeight( id ) : _getContainer( id ).height();
			if ( h === undefined ) {
				h = 0;
			}
			totalH += h === undefined ? 0 : h;
			if ( h != 0 ) {
				above = $(imgHolder).index() > index;
			}
			if ( totalH > imgHeight ) {
				break;
			}
		}
		
		var goalImageH = 0;
		
		goalImageH = totalH == 0 ? imgHeight : imgHeight - totalH;
		goalImageH = Math.max( 0, goalImageH );
		// calculate background-position offset
		var offset = Math.min(imgHeight, imgHeight-goalImageH);
		var p = above  ? "-"+String(offset) : "0";
		_goalPadding = goalImageH == 0 ? "0px" : "12px";	
		
		if ( totalH > 0 )
		{
			if ( !above )
			{
				$(img).removeClass("bottom");
				$(img).addClass("top");
			}
			 else
			{
				$(img).removeClass("top");
				$(img).addClass("bottom");
			
			}
		}
		if ( $animate ) {
			
			var speed = _moduleSpeed;
			
			$(imgHolder).stop().animate({height: goalImageH+"px", "margin-bottom": _goalPadding}, speed, _moduleEase);
		
			//$(img).css( !above ? {"bottom":"0px"} : {"top":"0px"});
			//$(img).stop().animate({top: p + "px"}, _moduleSpeed, _moduleEase);
			//$(img).stop().animate({backgroundPosition: "0px " + p + "px"}, _moduleSpeed, _moduleEase);
		}
		else
		{
			$(imgHolder).stop();
			$(img).stop();
			$(imgHolder).css({height: goalImageH+"px", display: goalImageH == 0 ? "none" : "block", "margin-bottom": _goalPadding});
			//$(img).css( !above ? {"bottom":"0px"} : {"top":"0px"});
//			$(img).css({top: p+"px"});
		//	$(img).stop().css({"background-position": "0px " + p + "px"});
		}
		//$(img).css( above ? {bottom: "0px"} : {top: "0px"});
		//$(img).css({top: (above ? p : "0")+"px"});
		
	}
	
	function _adjustImageSide( moduleHolder )
	{
		var checkOpen = $(moduleHolder).find(".module.open");
		if ( checkOpen.length > 0 )
		{
			$(moduleHolder).find(".image-holder").slideUp( _moduleSpeed, _moduleEase );
		} 
		 else
		{
			$(moduleHolder).find(".image-holder").slideDown( _moduleSpeed, _moduleEase );
		}
	}
	
	/* PUBLIC AUTO HEIGHT METHODS (CALLED BY MODULE)
	----------------------------------------- */
	
	function startAutoHeight( $id )
	{
		try{
			if ( _autoHeights.indexOf($id) == -1 )
			{
				_autoHeights.push( $id );
			}
		}catch(e){
			//alert("error"+e);
		}
		
	
		if ( !_isRunning )
		{
			//trace ("start auto height = " + $id );
			_startTimer();
		}
	}
	
	function stopAutoHeight( $id )
	{
		//trace ("stop auto height = " + $id );
		var ind = -1;
		for ( var i=0; i < _autoHeights.length; i++ )
		{
			if ( _autoHeights[i] == $id ) {
				ind = i;
			}
		}
		if ( ind == -1 ) {
			return;
		}
		//ind = _autoHeights.indexOf($id);
		if ( ind != -1 )_autoHeights.splice( ind, 1 );
		if ( _autoHeights.length == 0 ) _stopTimer();
		
	}
	
	// called when module is ready
	function onModuleLoaded( $id, $jsModule, $h )
	{
		//trace ("on module loaded = " + $id + ", module = " + $jsModule );
		_jsModules[$id] = $jsModule;
		module = _getModule($id);
		$(module).removeClass("loading");
		_setIframeHeight( $id, $h );
		openModule( $id );
		
		
		
	}
	// called when module height is updated
	function onModuleHeightUpdate( $id, $h )
	{
		_setModuleHeight( $id, $h );
	
	}
	
	function _getJSModule( $id )
	{
		return _jsModules[$id];
	}
	/* AUTO HEIGHT TIMER METHODS
	----------------------------------------- */
	
	function _startTimer()
	{
		_isRunning = true;
		_timerId = setTimeout( _onTimer, 5 );
	}
	function _stopTimer()
	{
		_isRunning = false;
		clearTimeout( _timerId );
		
	}
	function _onTimer()
	{
		for ( var i=0; i < _autoHeights.length; i++ )
		{
			_setModuleHeight( _autoHeights[i] );
		}
		_startTimer();
	}
	
	
	function trace( $str )
	{
		if ( debug ) debug.trace( $str );
	}
	
	/* EVENT LISTENING
	----------------------------------------- */
	function registerListener( $eventName, $func  )
	{
		if ( !_listeners[$eventName] ) _listeners[$eventName] = [];
		var listeners = _listeners[$eventName];
		listeners.push( $func );
		_listeners[$eventName] = listeners;
	}
	
	function _dispatchEvent( $eventName, $args )
	{
		var listeners = _listeners[$eventName];
		if ( listeners )
		{
			for ( var i=0; i < listeners.length; i++ )
			{
				func = listeners[i];
				try
				{
					func.apply( func, $args );
				}
				catch ( $e )
				{
					
				}
			}
		}
	}
	
	return {
		toggleModule 		: toggleModule,
		openModule 			: openModule,
		closeModule			: closeModule,
		loadModule 			: loadModule,
		onModuleLoaded		: onModuleLoaded,
		onModuleHeightUpdate: onModuleHeightUpdate,
		startAutoHeight		: startAutoHeight,
		stopAutoHeight		: stopAutoHeight,
		events				: events,
		registerListener	: registerListener
	}
}();
