/**
 * Peephole 
 */
var Peephole = Class.create();
Object.extend(Peephole.prototype, {

	/**
	 * Important objects
	 */
	ulObj: null,
	clonedActors: null,
	actorsWidth: null, 
	stageOffset: null, 
	
	/**
	 * Current index of position
	 */
	currentActor: null,
	currentActorIndex: 0,
	
	/**
	 * Localization
	 */
	texts: {
		'goLeft'		: '', 
		'goRight'		: ''
	},
	
	/**
	 * Blank image (a transparent GIF 1x1 pixel does it)
	 */
	blankImgSrc: '',
	
	/**
	 * Stores the number of actors on stage
	 */
	numActors: 0,
	
	/**
	 * Some options
	 */
	duration: null,
	autoPlay: null,
	cycleMode: null, 
	
	/**
	 * AutoPlay options/values
	 */
	autoPlayInterval: 10, // seconds
	autoPlayIntervalId: false,
	
	/**
	 * Initialize
	 * 
	 * @params array options Options to initialize instance
	 */
	initialize : function(options){
		this.target = $(options.target) || null;
		
		this.duration = options.duration || 0.25;
		this.autoPlay = options.autoPlay ? true : false;
		this.autoPlayInterval = options.autoPlayInterval || 10;
		this.cycleMode = (options.cycleMode==false) ? false : true;
		this.blankImgSrc = options.blankImg || '/img/blank.gif';
		
		options.texts = options.texts || {}; 
		this.texts.goLeft = options.texts.doLeft || '«';
		this.texts.goRight = options.texts.doRight || '»';
		
		// check if stage exists
		if( !this.target ){
			alert('Could not initialize Peephole instance: target html element not found');
			return false;
		}

		// remember important objects
		this.ulObj = this.target.down('.peephole-stage ul');
		
		// find all actors, clone and store them in this.clonedActors to keep
		// them as sources for later cloning
		var actorObjs = this.ulObj.childElements(),
			clonedActors = new Array();
		actorObjs.each(function(actorObj, i){
			// number actors
			actorObj.addClassName('peephole-actor');
			actorObj.addClassName('peephole-actor-' + i);
			// keep the clones
			clonedActors.push( actorObj.cloneNode(true) );
		});
		this.clonedActors = clonedActors;
		this.currentActor = actorObjs[0];
		this.numActors = this.clonedActors.length;

		// append clones
		if( this.cycleMode ){
			this.appendClonedActors('top');
			this.appendClonedActors('bottom');
		}
		// builds necessary html elements, like controlling links
		this.buildElements();
		
		
		// Calculate offset on stage
		var actorsWidth = this.target.down('ul li.peephole-actor').getWidth(),
		holeWidth = this.target.down('.peephole-hole').getWidth();
		this.stageOffset = Math.round( (holeWidth - actorsWidth) / 2 );
		this.actorsWidth = actorsWidth;
		
		// set width and starting position of <UL>
		var totalWidth = this.currentActor.getWidth() * this.numActors * 4; // should be 3, but we are generous
		this.ulObj.setStyle({ 'width': totalWidth  + 'px' });
		var x = this.currentActor.positionedOffset()[0] * -1 + this.stageOffset * 2;
		this.ulObj.setStyle({ 'left' : x - this.stageOffset + 'px' });
		
		// move to first position
		this.onShowActor(null, 0);
		
		if( this.autoPlay ){
			var _this = this;
			Event.observe(this.target, 'mouseout', this.onStartAutoPlay.bindAsEventListener(this));
			Event.observe(this.target, 'mouseover', this.onStopAutoPlay.bindAsEventListener(this));
			this.onStartAutoPlay();
		}
	},
	
	/**
	 * Build snecessary html elements
	 * 
	 */
	buildElements: function(){
		// add covering element (the hole)
		var divObj = Builder.node('div', {'class':'peephole-hole'});
		this.target.appendChild(divObj);
		
		// add a list for the controlling link elements
		var ulObj = Builder.node('ul', {'class':'peephole-controls'});
		this.target.appendChild(ulObj);
		
		// add control element
		var aObj = Builder.node('a', {'href':'#'}, Builder.node('span', this.texts.goLeft));
		Event.observe(aObj, 'click', this.onMoveInDirection.bindAsEventListener(this, 'left'));
		ulObj.appendChild(Builder.node('li', {'class':'peephole-go-left'}, aObj));
		
		// add control element to jump to each item
		for( i=0; i<this.numActors; i++ ){
			var classAtt = 'peephole-go-position peephole-go-position-' + i;
			var aObj = Builder.node('a', {'href':'#'}, Builder.node('span', i+1));
			Event.observe(aObj, 'click', this.onShowActor.bindAsEventListener(this, i));
			ulObj.appendChild(Builder.node('li', {'class':classAtt}, aObj));
		}
		
		// add control element
		var aObj = Builder.node('a', {'href':'#'}, Builder.node('span', this.texts.goRight));
		Event.observe(aObj, 'click', this.onMoveInDirection.bindAsEventListener(this, 'right'));
		ulObj.appendChild(Builder.node('li', {'class':'peephole-go-right'}, aObj));
		
		// add legend element
		var divObj = Builder.node('div', {'class':'peephole-legend'});
		this.target.appendChild(divObj);
		
	},
	
	/**
	 * Starts autoPlay loop
	 */
	onStartAutoPlay: function(){
		if( !this.autoPlayIntervalId ){
			this.autoPlayIntervalId = window.setInterval(this.onMoveInDirection.bind(this, null, 'right'), this.autoPlayInterval * 1000);
		}
	},
	
	/**
	 * Stops autoPlay loop
	 */
	onStopAutoPlay: function(){
		window.clearInterval(this.autoPlayIntervalId);
		this.autoPlayIntervalId = false;
	},
	
	/**
	 * Moves elements on stage to the left or right.
	 * 
	 * @param Event evt
	 * @param String dir either 'left' or 'right'
	 */
	onMoveInDirection: function(evt, dir){
		// stop event
		if( evt!=null ){
			Event.stop(evt);
		}

		switch( dir ){
			case 'left':
				var newActor = this.currentActor.previous();
				break;
			case 'right':
				var newActor = this.currentActor.next();
				break;
		}
		
		if( newActor != null ){
			this.moveStageToActor(newActor, true);
		}
		
		return false;
	},
	
	/**
	 * Move items to position identified by actorIndex
	 * 
	 * @param Integer actorIndex index of position to move to
	 */
	onShowActor: function(evt, actorIndex){
		// stop event
		if( evt!=null ){
			Event.stop(evt);
		}
		
		var newActor = this.currentActor;
		// search in which direction?
		if( this.currentActorIndex < actorIndex ){
			// search to the right
			newActor = this.currentActor.next('.peephole-actor-' + actorIndex);
		} else if( this.currentActorIndex > actorIndex ) {
			// search to the left
			newActor = this.currentActor.previous('.peephole-actor-' + actorIndex);
		}
		
		if( newActor != null ){
			this.moveStageToActor(newActor, false);
		}
		
		return false;
	},
	
	/**
	 * Moves images to position.
	 * 
	 * @param Integer actorIndex
	 * @param Boolean cycle if false cycling is disabled
	 */
	moveStageToActor: function(actorObj, cycle){
		var actorObjIndex = actorObj.className.match(/peephole-actor-(\d+)/)[1],
			oldActorIndex = this.currentActorIndex;
		
		// set current objects/values
		this.currentActor = actorObj;
		this.currentActorIndex = actorObjIndex;
		
		if( this.cycleMode && cycle){
			this.handleActorCycle(oldActorIndex, this.currentActorIndex);
		}
		
		// updateControls
		this.updateControls();
		this.imitateActorsInteractivity( this.currentActor );
		
		// find the actor to show, get its offset, and move there
		var x = actorObj.positionedOffset()[0];
		x -= this.stageOffset;
		x *= -1;

		// clear effect queue
		var queue = Effect.Queues.get('catalogbrowser');
		queue.each(function(effect) { effect.cancel(); });
		
		// animate
		new Effect.Move(this.ulObj, {
			'x'				: x, 
			'y'				: 0,
			'mode'			: 'absolute', 
			'duration'		: this.duration,
			'transition'	: Effect.Transitions.sinoidal,
			'queue'			: { position: 'end', scope: 'catalogbrowser', limit: 1 }
		});
	},
	
	/**
	 * Highlights active control
	 * 
	 */
	updateControls: function(){
		// highlight the according controls
		var liObjs = this.target.getElementsBySelector('ul.peephole-controls li.peephole-go-position'),
		_this = this;
		
		liObjs.each(function(liObj, i){
			var className = 'peephole-go-position-' + i + '-on';
			( i == _this.currentActorIndex ) ? liObj.addClassName(className) : liObj.removeClassName(className);
		});
	},

	/**
	 * Copies interactivity of actor to img.peephole-go-object
	 * 
	 */
	imitateActorsInteractivity: function( actorObj ){

		// build new .peephole-go-object
		try { this.target.down('.peephole-go-object').remove();	} catch(e){}
		var goObj = Builder.node('img', {'class':'peephole-go-object', 'src':this.blankImgSrc});
		this.target.appendChild(goObj);
		
		// clear the legend
		var legendObj = this.target.down('.peephole-legend');
		legendObj.innerHTML = '';

		var handler = actorObj.className.match(/peephole-actor-([a-z]*)/);
		try { handler = handler[1];	} catch(e){ return; }
	
		// handle handler
		switch( handler ){
		
			case 'imagemap':
				var usemap = actorObj.down('img').getAttribute('usemap');
				goObj.useMap = usemap;
				break;
				
			case 'link':
				if (actorObj.down('a') != null) {
					var href = actorObj.down('a').getAttribute('href');
					var aTarget = actorObj.down('a').getAttribute('target');
					goObj.style.cursor = 'pointer';
					Event.observe(goObj, 'click', function(){
					  if (aTarget=='_blank')
					    window.open(href);
					  else
						  window.parent.location = href;
					});
					var title = actorObj.down('a img').getAttribute('title');
					var aObj = Builder.node('a', {'href':href, 'target':'_parent'}, title);
					legendObj.appendChild( Builder.node('p', aObj) );
			  }			  
				break;
			
			case 'fsi':
				var fsiPagesDir = actorObj.down('a').getAttribute('rel');
				var title = actorObj.down('a img').getAttribute('title');
				goObj.style.cursor = 'pointer';
				var aObj = Builder.node('a', {'class':'fsi', 'rel':fsiPagesDir, 'href':'#'}, title);
				legendObj.appendChild( Builder.node('p', aObj) );
				$A([goObj, aObj]).each(function(triggerObj){
					Event.observe(triggerObj, 'click', function(evt){
						Event.stop(evt);
						fsiViewer.openViewer(triggerObj, fsiPagesDir);
					});
				});
				break;
				
		}
	},
	
	/**
	 * Handles the cycling of cloned actors on stage. This means that e.g. when the user has 
	 * the last actor in front and he still clicks to continue to the right, the first actor 
	 * will appear from the right again. When he sees the first one and clicks to the left, the 
	 * last one will appear from the left.
	 * This is done by 1. detecting a switch from last to first or first to last actor and 2. 
	 * by removing obsolete actor clones from very left/right and adding some to the very 
	 * right/left. Since the size of the <UL> has changed by doing so, we have to instantly 
	 * pan the <UL> to a new offset so that the move effect can start right after.
	 * 
	 * @param int oldActorIndex to detect a switch
	 * @param int newActorIndex to detect a switch
	 * 
	 */
	handleActorCycle: function(oldActorIndex, newActorIndex){
		var liObjs = this.ulObj.childElements();
		
		if( (oldActorIndex == this.numActors-1  &&  newActorIndex == 0) ){				// coming from left going right...
			
			this.removeClonedActors('top');
			this.appendClonedActors('bottom');
			
			var left = this.actorsWidth * this.numActors * -1 + this.actorsWidth + this.stageOffset;
			this.ulObj.setStyle({'left':left+'px'});
			
		} else if( (oldActorIndex == 0 &&  newActorIndex == this.numActors-1) ){		// coming from right going left...
			
			this.removeClonedActors('bottom');
			this.appendClonedActors('top');
			
			var left = this.actorsWidth * this.numActors * -1 * 2 + this.stageOffset;
			this.ulObj.setStyle({'left':left+'px'});
			
		}
	},
	
	/**
	 * Removes clones from 'top' (in hierarchy, but left on stage) or 
	 * 'bottom' (in hierarchy, but right on stage)
	 * 
	 * @param string where 'top' or 'bottom'
	 */
	removeClonedActors: function(where){
		var liObjs = this.ulObj.childElements();
		
		switch( where ){
			case 'top':
				for(i=0; i<this.clonedActors.length; i++){
					Element.remove(liObjs[i]);
				}
				break;
			case 'bottom':
				for( i = liObjs.length-1; i >= (liObjs.length-this.clonedActors.length); i-- ){
					Element.remove(liObjs[i]);
				}
				break;
		}
	},

	/**
	 * Adds clones to 'top' (in hierarchy, but left on stage) or 
	 * 'bottom' (in hierarchy, but right on stage)
	 * 
	 * @param string where 'top' or 'bottom'
	 */
	appendClonedActors: function(where){
		switch( where ){
			case 'top':
				for( i = this.clonedActors.length-1; i >= 0; i-- ){
					this.ulObj.insert( {'top' : this.clonedActors[i].cloneNode(true)} );
				}
				break;
			case 'bottom':
				for( i = 0; i < this.clonedActors.length; i++ ){
					this.ulObj.insert( {'bottom' : this.clonedActors[i].cloneNode(true)} );
				}
				break;
		}
	}
	
});
