var Stories = Class.create({
	
	initialize: function(target, options) {
		
		if($(target)) {
			this.target = $(target);
			this.target.removeClassName('nojs');
			
			// setup options
			this.options = Object.extend({
				animationDuration: 0.25, // how long does it take to animate
				animateInGroups: true, // animate in groups of [boolean]
				animateInGroupsOf: 4, // animate in groups of these many items
				animationDelayGrid: 0.1, // time in seconds to base delay on
				animationDelayDetails: 0.33, // time in seconds to wait before details window animates in
				contentContainer: document.viewport, // attempt to keep the overlay within a specific container (can be viewport or id of element)
				createToggles: true, // create a way to toggle between grid and list views [boolean]
				detailsWrapper: 'div.detail-wrapper', // detail elements
				defaultView: 'grid', // [grid/list] which way to display the stories by default 
				rowspan: 4, // how many items per 'row'
				scopeName: 'stories', // base name for scopes (used in animations)
				shuffle: true // should the array of elements be shuffled?
			}, options || {});
			
			// default display
			if(this.options.defaultView == 'grid') {
				this.els = this.target.select('li');
				this.hoverEls = this.target.select('li:not(.submit)');
				this.target.addClassName('grid');
			} else { 
				this.target.removeClassName('grid');
			} 
			
			// create toggles if option set to true
			if(this.options.createToggles) {
				this.createToggles();
			}
			
			
			
			// hide details
			this.details = this.target.select(this.options.detailsWrapper);
			this.details.invoke('hide');
			// set opacity to 0 on all elements
			this.els.invoke('setOpacity', 0); 
			
			// event caching
			this.events = {
				mouseenter: this.__mouseEnter.bindAsEventListener(this),
				mouseleave: this.__mouseLeave.bindAsEventListener(this)
			};
			
			this.hoverEls.invoke('observe', 'mouseenter', this.events.mouseenter);
			this.hoverEls.invoke('observe', 'mouseleave', this.events.mouseleave);
			
			
			
			// get last element
			var lastEl = this.els.pop();

			// get last row
			var lastRow = this.els.eachSlice(this.options.rowspan).last();
			var lastRowLength = lastRow.length;
			
			var colspan = this.options.rowspan - lastRowLength;
				colspan = (colspan == 0) ? this.options.rowspan : colspan;
			
			lastEl.addClassName('colspan-' + colspan);
			
			// check if the shuffle option is on
			var els = (this.options.shuffle) ? this._shuffle(this.els) : this.els;
			// group the elements
			els = els.eachSlice(this.options.animateInGroupsOf);
			// because we're queueing the animation at the end we need to
			// add the original last element to the first position in the last two-dimensional array
			// ex: [[el, el], [el, el, el], [NEW, el, el]]
			els.last().push(lastEl);

			for(var i = 0; i < els.length; i++) {
				for(var j = 0; j < els[i].length; j++) {
					this.animateGrid(els[i][j], i);
				}
			}
		}

	},
	
	createToggles: function() {
		/* <div class="story-layout-toggles">
			<a href="#">Grid</a><span>|</span><a href="#">List</a>
		</div>*/
		var div = new Element('div', {'class': 'layout-toggles'});
		var grid = new Element('a', {'href': '#', 'id': 'layout-grid', 'title': 'Grid'}).update('Grid');
		var list = new Element('a', {'href': '#', 'id': 'layout-list', 'title': 'List'}).update('List');
		div.insert(grid).insert(list);
		this.target.insert({top: div});
		
		(this.options.defaultView == 'grid') ? grid.addClassName('active') : list.addClassName('active');
		
		grid.observe('click', function(e) {
			e.stop();
			grid.addClassName('active');
			list.removeClassName('active');
			this.target.addClassName('grid');
			this.details.invoke('hide');
			this.hoverEls.invoke('observe', 'mouseenter', this.events.mouseenter);
			this.hoverEls.invoke('observe', 'mouseleave', this.events.mouseleave);
		}.bind(this));
		
		list.observe('click', function(e) {
			e.stop();
			grid.removeClassName('active');
			list.addClassName('active');
			this.target.removeClassName('grid');
			this.details.invoke('show');
			this.hoverEls.invoke('stopObserving', 'mouseenter', this.events.mouseenter);
			this.hoverEls.invoke('stopObserving', 'mouseleave', this.events.mouseleave);
		}.bind(this));
		
	},
	
	// fade in grid-view
	animateGrid: function(el, i) {
		var iterator = (this.options.animateInGroups) ? i : 0;
		//console.log(el, iterator);
		this.fx = new Effect.Opacity(el, { 
						from: el.getOpacity(), 
						to: 1,
						duration: this.options.animationDuration,
						delay: this.options.animationDelayGrid * iterator,
						queue: {
							position: 'end',
							scope: this.options.scopeName + '-' + iterator
						}
					});	
	},
			
	// shuffle the array
	// based on Fisher-Yates algorithm:
	// http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
	_shuffle: function(els) {
		var i = els.length;
		if (i > 0) {
			while (--i) {
				var j = Math.floor(Math.random() * (i + 1));
				var tempi = els[i];
				var tempj = els[j];
				els[i] = tempj;
				els[j] = tempi;
			}
			return els;
		} else {
			return false;
		}
	},
	
	_position: function(li, details) {
		// is there enough [viewport/container] space to add the overlay to the right of the opener?
		var detailsWidth = details.getWidth(); // width + additional offset
		var contentWidth = $(this.options.contentContainer).getWidth();
		
		contentWidth = (this.options.contentContainer != document.viewport) ? contentWidth + $(this.options.contentContainer).cumulativeOffset()[0] : contentWidth;
		
		var offset = li.cumulativeOffset()[0] + detailsWidth;
		
		//console.log('width of details %d, width of contentContainer %d, width of offset %d', detailsWidth, contentWidth, offset);
		// remove old position classnames
		details.removeClassName("west");
		
		if ((contentWidth >= detailsWidth) && (contentWidth <= offset)) {
			details.addClassName("west");
		}
	},
	
	// event handler for mouse enter
	__mouseEnter: function(e) {
		
		var li = e.findElement('li');
		var details = li.down(this.options.detailsWrapper);
		this.fadeIn(li, details);
	},
	
	// event handler for mouse leave
	__mouseLeave: function(e) {
		
		var li = e.findElement('li');
		var details = li.down(this.options.detailsWrapper);
		this.fadeOut(li, details);
	},
	
	fadeIn: function(li, details) {
		
		this.appearFx = new Effect.Opacity(details, {
			duration: this.options.animationDuration,
			from: 0,
			to: 0.99,
			delay: this.options.animationDelayDetails,
			queue: {
				position: 'end',
				scope: this.options.scopeName + '-details',
				limit: 2
			},
			afterSetup: function(e) {
				// check for position
				this._position(li, details);
				// remove all other active classes - should only be one at a time
				this.els.invoke('removeClassName', 'active');
				li.addClassName('active');
				details.setOpacity(0).show();
			}.bind(this)
		});
		
	},
	
	fadeOut: function(li, details) {
		
		if (this.appearFx && this.appearFx.state == 'finished') {
			this.fadeFx = new Effect.Fade(details, {
				duration: this.options.animationDuration / 2
			});
		} else {
			// we're not done animating-in the overlay... i.e. we cancelled, so hide immidiately
			if (this.appearFx) {
				this.appearFx.cancel();
			}
			details.hide();
		}
		this.els.invoke('removeClassName', 'active');
	}
	
});
