/**
 * Class: String
 */
String.implement({
	/**
	 * Method: getRandomId
	 * 		Generates a random numeric string
	 * 
	 * Example:
	 * 		var str = 'Random'.getRandomId();
	 */
    getRandomId:function(){
        var r = $random(0,1000) * $random(0,1000);
        return !$(this+r) ? this+r : this.getRandomId();
    }
});

/**
 * Class: Moobox
 * 		Creates a modal window
 * 
 * Arguments:
 * 		options (json) - See options below
 * 
 * Options:
 * 		zIndex (int) - z-index level for the modal window.
 * 		showOverlay (bool) - Show or don't show page overlay when the modal window opens
 * 		showTitle (bool) - Make the modal window title visible or hidden
 * 		dragabble (bool) - Make the modal window drabble or not
 * 		externalCSS (string) - URL to an external stylesheet to style the modal window
 * 		classes (json) - CSS classes to style the modal window
 * 			- overlay - Page overlay CSS class
 * 			- window - Modal window CSS class
 * 			- content - Modal window content element CSS class
 * 			- closer - Modal window close button CSS class
 * 			- titlebar - Modal window title bar CSS class
 * 			- loading - Modal window "loading" class (toggled on the modal window element when new content is loaded)
 * 		fxDuration (json) - FX duration settings
 * 			- window - Modal window animation duration (milliseconds)
 * 			- overlay - Page overlay animation duration (milliseconds)
 * 		size (json) - Modal window dimensions in default states
 * 			- closed - Size of the closed modal window
 * 			- initial - Size of the open modal window before any content was loaded
 * 			- _default - Default size of the open modal window, after content was loaded, in case the
 * 						 script could not determine the dimensions of the loaded content
 */
var Moobox = new Class({
	Implements: [Events, Options, Chain],
	options:{
		zIndex:10000,
		showOverlay:true,
		showTitle:true,
		dragable:true,
		externalCSS:null,
		classes:{
			overlay:'mb-overlay',
			window:'mb-window',
			content:'mb-content',
			closer:'mb-closer',
			titlebar:'mb-titlebar',
			loading:'mb-loading'
		},
		fxDuration:{
			window:750,
			overlay:200
		},
		size:{
			initial:{x:80,y:50},
			_default:{x:600,y:450}
		}
	},
	/**
	 * Method: initialize
	 * 		Initializes the modal class
	 */
	initialize:function(options){
		this.setOptions(options);
		this.isOpen = false;
		this.loading = false;
		this.build();
	},
	/**
	 * Method: build
	 * 		Builds the modal window components (window, content, title bar, close button, page overlay)
	 */
	build:function(){
		if(this.options.externalCSS != null){
			new Asset.css(this.options.externalCSS);
		}
		this.content = new Element('div',{
			'class':this.options.classes.content
		});
		this.closer = new Element('a',{
			href:'#',
			'class':this.options.classes.closer,
			events:{
				'click':this.close.bindWithEvent(this)
			}
		}).set('html','close');
		this.title = new Element('h3',{
			styles:{
				display:'none'
			}
		}).set('html','&nbsp;');
		this.titlebar = new Element('div',{
			'class':this.options.classes.titlebar
		}).adopt(this.title,this.closer);
		this.window = new Element('div',{
			'class':this.options.classes.window,
			styles:{'display':'none','z-index':this.options.zIndex+1}
		}).adopt(this.titlebar,this.content);
		this.overlay = new Element('div',{
			'class':this.options.classes.overlay,
			styles:{'display':'none','z-index':this.options.zIndex,'background':'#000'}
		}).set('html','&nbsp;');
		this.tmp = new Element('div',{
			styles:{'position':'absolute','top':'-1000em','left':'-1000em'}
		});
		$(document.body).adopt(this.overlay,this.window,this.tmp);
		
		/**
		 * Add an iframe below the content element to fix IE6 select element z-index bug
		 */
		if(Browser.Engine.trident4){
			var iframe1 = new Element('iframe',{
				styles:{'position':'absolute','width':'104%','height':'110%','top':'-5%','left':'-2%','z-index':'-1'},
				height:'100%',
				width:'100%',
				frameborder:0
			}).setOpacity(0).injectTop(this.window);
			if(this.options.showOverlay){
				var iframe2 = new Element('iframe',{
					styles:{'position':'absolute','width':'120%','height':'120%','top':'-10%','left':'-10%'},
					height:'100%',
					width:'100%',
					frameborder:0
				}).setOpacity(0).injectTop(this.overlay);
			}
		}
		
		/**
		 * Make modal window dragable if required
		 */
		if(this.options.dragable){
			this.drag = new Drag(this.window, {
				snap: 0,
				handle: this.titlebar,
				onSnap: function(el){
					el.addClass('dragging');
				},
				onComplete: function(el){
					el.removeClass('dragging');
				}
			});
		}
		
		/**
		 * Add animation effects to the overlay and modal window
		 */
		this.effects = {
			overlay: new Fx.Tween(this.overlay,{
				onStart: Events.prototype.clearChain,
				onComplete: function(){this.overlay.setStyle('height',document.getScrollSize().y+'px');}.bind(this),
				property: 'opacity',
				duration: this.options.fxDuration.overlay,
				link: 'cancel'
			}).set(0),
			window: new Fx.Morph(this.window,{
				onStart:Events.prototype.clearChain,
				unit: 'px',
				duration: this.options.fxDuration.window,
				transition: Fx.Transitions.Quint.easeOut,
				link: 'cancel',
				unit: 'px'
			}),
			content:new Fx.Tween(this.content,{
				onStart: Events.prototype.clearChain,
				property: 'opacity',
				duration: this.options.fxDuration.overlay,
				link: 'cancel'
			}).set(0)
		}
	},
	/**
	 * Method: bindOpeners
	 * 		Binds DOM elements as openers for the modal window
	 * 
	 * Arguments:
	 * 		els (DOM element/elements) - Reference to a single or a collection of DOM elements on the page
	 * 
	 * Comments:
	 * 		Passed elements must be links and contain at least an "href" attribute
	 * 		If the passed elements contain a "title" attribute, its value will be set as the modal window's
	 * 		title when the element is clicked
	 */
	bindOpeners:function(els){
		this.openers = $splat(els);
		if(this.openers.length == 0) {
			return;
		}
		var _this = this;
		this.openers.addEvent('click',function(e){
			var ev = new Event(e);
			ev.preventDefault();
			_this.onOpenerClick(this);
		});
	},
	/**
	 * Method: onOpenerClick
	 * 		Callback for the 'click' event of the bound openers. Extracts the URL from the target elements and
	 * 		uses it to load new content into the modal window
	 * 
	 * Arguments:
	 * 		el (Event target) - Event target element
	 */
	onOpenerClick:function(el){
		var url = el.get('href') || false;
		var title = el.get('title') || '';
		this.title.set('html',title).setStyle('display','none');
		if(url){
			this.getContent(url,title);
		}
	},
	/**
	 * Method: getContent
	 * 		Calls the relevant content handler that loads new content into the modal window
	 * 
	 * Arguments:
	 * 		url (string) - The URL from which the content is loaded
	 * 		title (string) - The new title for the modal window
	 */
	getContent:function(url,title){
		/**
		 * Parse the passed URL and detect which content type its pointing to.
		 * The detected content type is used to call the relevant content handler
		 */
		var type = url.match(/\.(?:jpg|jpeg|png|gif)$/i) ? 'image' : (url.match(/\.(?:swf)$/i) ? 'flash' : (url.match(/http:\/\//) && !url.match(document.domain) ? 'iframe' : 'ajax'));
		var f = function(){
			this.toggleLoading(true); //toggle the "loading" class on the modal window
			Moobox.preloaders[type].call(this,url,title); //call the relevant content handler
		}.bind(this);
		if(this.isOpen){
			f();//just fetch new content
		} else{
			//check if the page overlay needs to be shown before new content is fetched
			if(this.options.showOverlay && !this.overlay.retrieve('opacity')){
				this.chain.apply(this,[this.toggleOverlay(),this.open(),f.delay(this.options.fxDuration.overlay+this.options.fxDuration.window,this)]);
			} else {
				this.chain.apply(this,[this.open(),f.delay(this.options.fxDuration.window,this)]);
			}
		}
	},
	/**
	 * Method: setContent
	 * 		Adds the new content to the modal window content element
	 * 
	 * Arguments:
	 * 		content (DOM element) - The new content to add to the content element
	 * 		size (json) - The size of the new content in the format of {x:width,y:height}
	 * 		title (string) - The new modal window title
	 */
	setContent:function(content,s,title){
		if(!this.isOpen){
			return;
		}
		var size = $type(s) != 'object' ? this.options.size._default : s;
		this.currentTitle = title;
		if(this.options.showTitle){
			this.title.set('html',this.currentTitle).setStyle('display','none');
		}
		this.content.empty().adopt(content);
		this.position(size);
		this.showContent.delay(this.options.fxDuration.window,this);
	},
	/**
	 * Method: showContent
	 * 		Shows the newly added content by fading in the modal window's content element
	 */
	showContent:function(){
		this.toggleLoading(false);
		if(this.options.showTitle){
			this.title.setStyle('display','block');
		}
		this.effects.content.start(1);
		this.fireEvent('onUpdate',[this.window]);
	},
	/**
	 * Method: toggleOverlay
	 * 		Toggles the page overlay on or off
	 * 
	 * Arguments:
	 * 		state (bool) - Turn the overlay on (True) of off (False)
	 */
	toggleOverlay:function(state){
		var opacity = state || this.overlay.retrieve('opacity');
		this.overlay.setStyle('display',opacity ? 'none' : 'block');
		this.effects.overlay.start(opacity ? 0 : 0.4);
	},
	/**
	 * Method: toggleLoading
	 * 		Toggles the modal window's "loading" CSS class on or off.
	 * 		Toggles the modal window's internal "loading" state on or off
	 * 
	 * Arguments:
	 * 		state (bool) - Turn the "loading" CSS class on (True) of off (False)
	 */
	toggleLoading:function(state){
		if(state){
			if(this.currentContent){
				this.currentContent.setStyle('display','none');
			}
			this.effects.content.set(0);
		}
		this.window.toggleClass(this.options.classes.loading);
		this.loading = state;
	},
	/**
	 * Method: open
	 * 		Opens the modal window to its initial position before loading content
	 */
	open:function(){
		this.loading = true;
		this.setInitialPosition();
		this.effects.window.start({opacity:1});
		this.isOpen = true;
		this.fireEvent('onOpen',[this.window]);
	},
	/**
	 * Method: close
	 * 		Closes the modal window
	 * 
	 * Arguments:
	 * 		ev (Event) - The event that called the close() method
	 */
	close:function(ev){
		if(ev){
			ev.preventDefault();
		}
		var params = $merge({
			opacity:0
		},this.getWindowDimentions(this.options.size.initial));
		this.effects.window.start(params);
		if(this.options.showOverlay){
			this.toggleOverlay.delay(this.options.fxDuration.window,this);
		}
		this.content.empty();
		if(this.request){
			this.request.cancel();
		}
		this.isOpen = false;
		this.fireEvent('onClose',[this.window]);
	},
	/**
	 * Method: position
	 * 		Positions the modal window according to the passed dimensions object
	 * 
	 * Arguments:
	 * 		size (json) - The target size of the modal window in the format of {x:width,y:height}
	 */
	position:function(size){
		this.effects.window.start(this.getWindowDimentions(size));
	},
	/**
	 * Method: getWindowDimentions
	 * 		Gets the dimensions of the current document element that are used as reference to position the modal
	 * 
	 * Arguments:
	 * 		size (json) - The target size of the modal window in the format of {x:width,y:height}
	 * 
	 * Returns:
	 * 		params (json) - The new coordinates and size the modal window should be animated and positioned to
	 */
	getWindowDimentions:function(size){
		var docSize = document.getSize(), scroll = document.getScroll();
		var params = {
			left: (scroll.x + (docSize.x - size.x) / 2).toInt(),
			right: (scroll.x - (docSize.x - size.x) / 2).toInt(),
			top: (scroll.y + (docSize.y - size.y) / 2).toInt(),
			height:(size.y).toInt(),
			width: (size.x).toInt()
		};
		return params;
	},
	setInitialPosition:function(){
		this.window.setStyles($merge({
			display:'block',
			opacity:0
		},this.getWindowDimentions(this.options.size.initial)));
	}
});

/**
 * Class: Moobox
 * 
 * Hash:
 * 		Moobox.preloaders - A hash of functions that handle the different content types supported by the Moobox class			
 */
Moobox.preloaders = new Hash({
	image:function(url,title){
		var img = new Asset.image(url, {
			onload: function(){
				var content = img;
				var size = {x:img.width,y:img.height};
				this.fireEvent('onLoad',[this.window]);
				this.setContent(content,size,title);
			}.bind(this)
		});
	},
	ajax:function(url,title){
		this.request = new Request({
			url:url,
			method: 'get',
			evalScripts:true,
			headers: {'X-Requested-With':'XMLHttpRequest'},
			autoCancel:true,
			onSuccess: function(response){
				this.tmp.set('html',response);
				var content = this.tmp.getFirst();
				var size = content.getSize();
				size.x = size.x < this.options.size.initial.x ? this.options.size.initial.x : size.x;
				size.y = size.y < this.options.size.initial.y ? this.options.size.initial.y : size.y;
				this.fireEvent('onLoad',[this.window]);
				this.setContent(content,size,title);
			}.bind(this)
		}).send();
	},
	flash:function(url,title){
		var id = 'Swiff'.getRandomId();
		var div = new Element('div',{
			id:id,
			styles:{
				width: this.options.size._default.x,
				height: this.options.size._default.y
			}
		});
		this.tmp.empty().adopt(div);
		var obj = new Swiff(url, {
			width: this.options.size._default.x,
			height: this.options.size._default.y,
			container:id,
			params: {
				wmode: 'transparent'
			}
		});
		var content = this.tmp.getFirst();
		var size = this.options.size._default;
		this.fireEvent('onLoad',[this.window]);
		this.setContent(content,size,title);
	},
	iframe:function(url,title){
		var iframe = new Element('iframe',{
			src:url,
			width: this.options.size._default.x,
			height: this.options.size._default.y,
			frameborder:0
		});
		this.tmp.empty().adopt(iframe);
		var content = this.tmp.getFirst();
		var size = this.options.size._default;
		this.fireEvent('onLoad',[this.window]);
		this.setContent(content,size,title);
	}
});

/**
 * Class: Moobox.Stack
 * 		Extends the base Moobox class and adds content-stacking capabilities
 * 
 * Options:
 * 		See Moobox options above
 * 
 * 		- links (json) - CSS classes for openers and closers links inside the content window
 * 			- closers - CSS class for links inside loaded content that should close the modal window
 * 			- openers - CSS class for links inside loaded content that should load new content into the modal window
 * 
 * Extends: Moobox
 */
Moobox.Stack = new Class({
	Extends: Moobox,
	options:{
		links:{
			closers:'mb-internal-closer',
			openers:'mb-internal-opener'
		}
	},
	initialize:function(options){
		this.contentStack = [];
		this.titleStack = [];
		this.parent(options);
	},
	/**
	 * Method: setContent
	 * 		Adds the new content to the modal window content element
	 * 
	 * Arguments:
	 * 		content (DOM element) - The new content to add to the content element
	 * 		size (json) - The size of the new content in the format of {x:width,y:height}
	 * 		title (string) - The new modal window title
	 */
	setContent:function(content,s,title){
		if(!this.isOpen){
			return;
		}
		var size = $type(s) != 'object' ? this.options.size._default : s;
		this.currentContent = new Element('div',{
			styles:{'display':'none'}
		}).adopt(content);
		this.currentTitle = title;
		this.title.set('html',this.currentTitle).setStyle('display','none');
		this.content.adopt(this.currentContent);
		this.contentStack.push(this.currentContent);
		this.titleStack.push(title);
		this.bindInternalLinks();
		this.position(size);
		this.showContent.delay(this.options.fxDuration.window,this);
	},
	/**
	 * Method: showContent
	 * 		Shows the newly added content by fading in the modal window's content element
	 */
	showContent:function(){
		this.currentContent.setStyle('display','block');
		this.parent();
	},
	/**
	 * Method: getLastContent
	 * 		Gets the highest-stacked content element from the content elements stack.
	 * 		Used to reveal a previous content element after the last content element in the stack was closed (removed from stack)
	 */
	getLastContent:function(){
		this.effects.content.set(0);
		this.currentContent = this.contentStack.getLast().setStyle('display','block');
		this.currentTitle = this.titleStack.getLast();
		this.title.set('html',this.currentTitle).setStyle('display','none');
		var size = this.currentContent.getFirst().getSize();
		size.x = size.x < this.options.size.initial.x ? this.options.size.initial.x : size.x;
		size.y = size.y < this.options.size.initial.y ? this.options.size.initial.y : size.y;
		return size;
	},
	/**
	 * Method: bindInternalLinks
	 * 		Binds links inside the content element and adds content loading and closing capabilites
	 */
	bindInternalLinks:function(){
		this.bindOpeners(this.currentContent.getElements('.'+this.options.links.openers));
		this.bindClosers(this.currentContent.getElements('.'+this.options.links.closers));
	},
	/**
	 * Method: bindClosers
	 * 		Binds internal links to the closeCurrent class method. Bound links will close the current content element
	 * 
	 * Arguments:
	 * 		els (DOM element/elements) - A single or collection of DOM elements to bind to the closeCurrent class method
	 */
	bindClosers:function(els){
		var closers = $splat(els);
		if(closers.length == 0) return;
		var _this = this;
		closers.addEvent('click',function(e){
			var ev = new Event(e);
			ev.preventDefault();
			_this.closeCurrent();
		});
	},
	/**
	 * Method: closeCurrent
	 * 		Removes the current (visible) content element from the modal window content stack and
	 * 		reveals the one under it (if exists) or closes the modal window (if doesnt exist)
	 */
	closeCurrent:function(){
		this.contentStack.pop();
		this.titleStack.pop();
		this.currentContent.dispose();
		if(this.contentStack.length > 0){
			var size = this.getLastContent();
			this.position(size);
			this.showContent.delay(this.options.fxDuration.window,this);
		} else{
			this.close();
		}
	}
});

/**
 * Class: Moobox.Gallery
 * 		Extends Moobox and adds image-gallery capabilities
 * 
 * Options:
 * 		See Moobox options above
 * 
 * 		- classes (json) - CSS classes styling modal elements (extends Moobox options.classes)
 * 			- arrows - CSS class gallery navigation arrows container
 * 			- next - CSS class gallery navigation "next" arrow
 * 			- previous - CSS class gallery navigation "previous" arrow
 * 		- fxDuration (json) - FX duration settings
 * 			- arrows - Navigation arrows fade-in animation duration (milliseconds)
 * 			- arrowsDelay - Delay before hiding navigation arrows when the cursor leaves the modal window area (milliseconds)
 * 		- arrows (json) - defines gallery navigation parameters
 * 			- animate (bool) - Whether to animate the gallery navigation arrows or now
 * 			- opacity (int) - Opacity level of the gallery navigation area (0-1)
 * 		- preload (bool) - Whether to preload images or not
 * 
 * Extends: Moobox
 */
Moobox.Gallery = new Class({
	Extends: Moobox,
	options:{
		classes:{
			arrows:'mb-arrows',
			next:'mb-next',
			previous:'mb-previous'
		},
		fxDuration:{
			arrows:100,
			arrowsDelay:1000
		},
		arrows:{
			animate:true,
			opacity:0.6
		},
		preload:true
	},
	initialize:function(options){
		this.parent(options);
		this.currentArrowIndex = 0;
		this.timer = null;
	},
	/**
	 * Method: build
	 * 		Builds the modal window components
	 */
	build:function(){
		this.parent();
		this.next = new Element('a',{
			href:'#',
			'class':this.options.classes.next,
			events:{
				'click':this.onArrow.bindWithEvent(this)
			}
		}).set('html','Next &raquo;');
		this.previous = new Element('a',{
			href:'#',
			'class':this.options.classes.previous,
			events:{
				'click':this.onArrow.bindWithEvent(this)
			}
		}).set('html','&laquo; Previous');
		this.arrows = new Element('div',{
			'class':this.options.classes.arrows
		}).adopt(this.previous,this.next).injectInside(this.window);
		this.effects.arrows = new Fx.Tween(this.arrows,{
			onStart: Events.prototype.clearChain,
			property: 'opacity',
			duration: this.options.fxDuration.arrows,
			link: 'cancel'
		}).set(0);
		if(this.options.arrows.animate){
			this.window.addEvents({
				'mouseover':function(){
					if(this.timer){
						$clear(this.timer);
					}
					if(this.arrows.get('opacity') == 0 && this.loading == false){
						this.toggleArrows(true);
					}
				}.bind(this),
				'mouseout':function(){
					this.timer = this.toggleArrows.delay(this.options.fxDuration.arrowsDelay,this,[false]);
				}.bind(this)
			});
		}
	},
	/**
	 * Method: onArrow
	 * 		Handles clicks on the next/previous arrows and loads the relevant image into the window
	 */
	onArrow:function(ev){
		ev.preventDefault();
		var el = $(ev.target);
		this.onOpenerClick(this.openers[this.hrefs.indexOf(el.get('href'))]);
	},
	/**
	 * Method: showContent
	 * 		Shows the newly loaded content in the modal window
	 */
	showContent:function(){
		this.parent();
		this.updateArrows();
		if(!this.options.arrows.animate && this.arrows.get('opacity') == 0 && !this.loading){
			this.toggleArrows(true);
		}
	},
	/**
	 * Method: toggleArrows
	 * 		Shows or hides the gallery navigation arrows
	 * 
	 * Arguments:
	 * 		state (bool) - Show (True) or hide (False) the gallery navigation arrows
	 */
	toggleArrows:function(state){
		var op = state ? this.options.arrows.opacity : 0;
		this.effects.arrows.start(op);
	},
	/**
	 * Method: bindOpeners
	 * 		Binds the passed elements as modal openers
	 * 
	 * Arguments:
	 * 		els (DOM element/elements) - A single or collection of DOM elements
	 */
	bindOpeners:function(els){
		this.parent(els);
		this.hrefs = this.openers.get('href');
		if(this.options.preload){
			var imgs = new Asset.images(this.hrefs);
		}
	},
	/**
	 * Method: onOpenerClick
	 * 		Handles clicks on modal opener elements
	 */
	onOpenerClick:function(el){
		this.parent(el);
		this.currentArrowIndex = this.hrefs.indexOf(el.get('href'));
	},
	/**
	 * Method: updateArrows
	 * 		Updates the URL and title values of the navigation arrows based on the currently loaded images
	 */
	updateArrows:function(){
		var previous = this.currentArrowIndex == 0 ? this.openers.getLast() : this.openers[this.currentArrowIndex-1];
		var next = this.currentArrowIndex == this.openers.length-1 ? this.openers[0] : this.openers[this.currentArrowIndex+1];
		this.previous.set('href',previous.get('href'));
		this.next.set('href',next.get('href'));
	},
	/**
	 * Method: reset
	 * 		Resets the modal class to its initial position, with arrows pointing to the first image in the openers collection
	 */
	reset:function(){
		if(this.isOpen){
			this.onOpenerClick(this.openers[0]);
		}
		this.updateArrows();
	}
});

