if (Object.isUndefined(PROTO)) { var PROTO = { } }

Object.extend(PROTO, {
	cachedImages: [],
	json: {}
});

PROTO.utils = {
	
	/**
	 * Removes class from all the siblings of an element, then adds class to an element
	 * 
	 * @param {Object} el element to add class name to
	 * @param {String} className class name to toggle
	 */
	toggleSiblingsClassName: function(el, className) {
		//el.siblings().invoke('removeClassName', className) gives slight delay => replacing with regular loop
		var siblings = el.siblings();
		for (var i=0, j=siblings.length; i<j; ++i) {
			siblings[i].removeClassName(className);
		}
		el.addClassName(className);
	},
	
	/**
	 * Sorts actual DOM elements according to scripts array passed to it.
	 * Each object in scripts has .ref which points to DOM element in the document
	 * Looping over those elements, inserting them in correct order (as in scripts)
	 * 
	 * @param {Array} scripts array of scripts to loop over
	 */
	sortNodes: function(scripts) {
		scripts.each(function(script, index){
			var a = script.ref, li = a.parentNode, ul = li.parentNode;
			a[index ? 'removeClassName' : 'addClassName']('first');
			ul.insertBefore(li, ul.childNodes[index]);
		});
	},
	
  // disable scrollbar if there's nothing to scroll
	toggleScrollbar: function() {
			
		var container = $('leftContainerMid'),
			list = container.down('ul'),
			scrollbar = $('scrollbar');
		
		// "scroll" to the top
		PROTO.slider.setValue(PROTO.slider.range.start);
		container.scrollTop = 0;
		
		if (list.scrollHeight <= container.offsetHeight) {
			PROTO.slider.setDisabled();
			scrollbar.setOpacity(0.3);
		}
		else {
			PROTO.slider.setEnabled();
			scrollbar.setOpacity(1);
		}
	},
	
	random: function(min, max) {
		return Math.floor(Math.random() * (max - min + 1)) + min;
	},
	// for clearing all the unique ids such as i21, s21, p21 => 21
	parseId: function(id) {
		return id.match(/(\d+)/)[0];
	}
}

Object.extend(PROTO, {
	
	// also see PROTO.config object for global "server based" variables
	
	SCRIPT_INFO_SEPARATOR: /\s*\|\s*/,
	SCRIPT_TAG_SEPARATOR: /\s*,\s*/,
	
	SCROLL_DELTA: 2.227,        // pixels
	FLASH_DELAY: 2,             // seconds
	TAG_MORPH_DURATION: 0.3,    // seconds
	FLASH_APPEAR_DURATION: 0.3, // seconds
	
	IMAGES_TO_CACHE: [ 
		'right-pressed.png', 
		'left-depressed.png',
		'login-container-bg.png',
		'login-input-bg.png',
		'error.png',
		'success.png',
		'shadow-rounded.png',
		'loading-indicator.gif',
		'signIn-btn.png'
	],
	
	EMPTY_IMAGE_URL: 'empty.png',
	LOADING_INDICATOR_URL: 'loading-indicator.gif',
	
	LOGIN_FAILED_MSG: 'Authorization failed. Please try again',
	EMPTY_FIELDS_MSG: 'Name and password fields can not be empty'
})

/**
 * @className			Tag
 * @classDescription	Creates a tag to be used in tagCloud object
 */

PROTO.Tag = Class.create({
	
	/**
	 * @constructor
	 * @param {String} name Name of a tag (coming most likely from innerHTML)
	 * @param {HTMLElement} ref Reference to HTMLELement corresponding to this tag
	 * @param {Boolean} state Indicates wheather tag is selected or not (defaults to false)
	 */
	initialize: function(name, ref, selected) {
		this.name = name;
		this.ref = ref;
		this.selected = selected || false;
	},
	
	select: function() {
		if (!this.selected) {
			this.selected = true;
			this.ref.firstChild.addClassName('selected').setStyle({background: '#f60', color: '#fff'});
		}
	},
	
	deselect: function() {
		if (this.selected) {
			this.selected = false;
			var el = this.ref.firstChild;
			new Effect.Morph(el, {
				style: 'background:#fff; color:#f60',
				duration: PROTO.TAG_MORPH_DURATION,
				afterFinish: function(){
					el.className = '';
				},
				queue: {position: 'end', scope: 'tag' + this.name, limit: 1}
			})
			
		}
	}
})

/**
 * @className 			TagCloud
 * @classDescription	Stores collection of Tag objects, 
 *						responsible for selecting/deselecting tags, firing events
 */

PROTO.TagCloud = Class.create({
	/**
	 * @constructor
	 * @param {Object} tags Array of tags to store in tag cloud
	 */
	initialize: function(tags) {
		this.tags = tags;
		this.length = tags.length;
		this.container = $('tagCloudContainer');
		this.multiTags = false;
		this.initBehaviour();
	},
	
	initBehaviour: function() {
		this.tags.each(function(el){
			$(el.ref).observe('click', this.onClick.bind(this))
		}.bind(this));
	},
	
	onClick: function(e) {
		e.stop();
		this.multiTags = e.ctrlKey;
		var target = e.target;
		this[target.hasClassName('selected') ? 'deselectTag' : 'selectTag'](target.parentNode._index)
		document.fire('tagCloud:updated');
	},
	
	selectTag: function(index) {
		if (!this.multiTags) {
			this.tags.invoke('deselect');
		}
		this.tags[index].select();
	},
	
	deselectTag: function(index) {
		if (!this.multiTags) {
			for (var i=0, l=this.tags.length; i<l; ++i) {
				this.tags[i].deselect();
			}
		}
		this.tags[index].deselect();
	},
	
	deselectAll: function() {
		for (var i=0, l=this.tags.length; i<l; ++i) {
			this.deselectTag(i);
		}
		document.fire('tagCloud:updated');
	},
	
	getSelectedTags: function() {
		var selectedTags = [];
		this.tags.each(function(el){
			if (el.selected) {
				selectedTags.push(el.name);
			}
		});
		return selectedTags;
	}
})

PROTO.Script = Class.create({
	/**
	 * @constructor
	 * @param {HTMLElement} Actual HTMLElement this script will be corresponding to
	 */
	initialize: function(ref) {
		this.ref = ref;
		this.id = ref.id.match(/(\d+)/)[0];
		this.name = ref.innerHTML;
		this.formattedName = this.ref.href.match(/([^\/]*)$/g)[0];
		this.selected = false;
		this.visible = true;
		this.tags = [];
		this.size = null;
		
		// this is where json will be stored
		this.data = null;
		
		if (ref.hasAttribute('rel')) {
			var info = ref.getAttribute('rel').split(PROTO.SCRIPT_INFO_SEPARATOR);
			this.tags = info[0].split(PROTO.SCRIPT_TAG_SEPARATOR);
			this.size = info[1];
		}
	},
	
	select: function() {
		if (!this.selected && this.visible) {
			this.ref.addClassName('selected');
			this.selected = true;
		}
	},
	
	deselect: function() {
		this.ref.removeClassName('selected');
		this.selected = false;
	},
	
	show: function() {
		this.visible = true;
		this.ref.parentNode.style.display = '';
	},
	
	hide: function() {
		this.visible = false;
		this.ref.parentNode.style.display = 'none';
	}
});

/**
 * Creates object which can be used to reference all the scripts in the container
 */
PROTO.ScriptsContainer = Class.create({
	initialize: function(container) {
		
		this.scripts = [];
		this.sortedBy = 'name';
		this.counter = $('totalScriptsCounter');
		this.counterWord = $$('#totalScripts span')[1];
		
		this.container = $(container);
		
		if (this.container) {
			this.containerBottomPos = this.container.cumulativeOffset()[1] + this.container.getHeight();
			this.ul = this.container.down('ul');
			
			this.initScripts();
			this.initBehaviour();
			this.initScrolling();
			
			this.scripts.each(function(script, index){ script._index = index; });
		}
	},
	/**
	 * This one acts as a controller for script selection,
	 * since other objects (i.e. AuxContainer) need to modify scripts' state,
	 * we can intercept these calls right here and invoke other stuff (i.e. scrollToSelected)
	 * 
	 * @param {String} script index of a script to select
	 * @param {String} _static if set to true, server request will not be initiated
	 */
	selectScript: function(id, _static) {
		
		id = PROTO.utils.parseId(id);
		this.clearAll();
		
		// clearing loading indicator image if present
		if ($('loading-indicator')) 
			$('loading-indicator').remove();
		
		// select by id if id was passed else just select first one
		var found = id ? this.find(id) : this.scripts[0];
		this.selectedScript = found;
		if (!_static) this.loadScript(found.id);
		
		found.select();
		//dhtmlHistory.add(found.id);
		this.scrollToSelected();
	},
	
	find: function(id) {
		return this.scripts.find(function(el){return el.id == id});
	},
	sortByName: function() {
		if (this.sortedBy !== 'name') {
			this.scripts.sort(function(a,b){
				aName = a.name.toLowerCase(), bName = b.name.toLowerCase();
				return (bName < aName) - (aName < bName);
			});
			PROTO.utils.sortNodes(this.scripts);
			this.sortedBy = 'name';
		}
	},
	sortBySize: function() {
		if (this.sortedBy !== 'size') {
			this.scripts.sort(function(a,b){
				return a.size - b.size;
			});
			PROTO.utils.sortNodes(this.scripts);
			this.sortedBy = 'size';
		}
	},
	
	/**
	 * Walks all the <a> elements in a given container, 
	 * creates object:Tag for each and binds actual DOM element to .ref property
	 * 
	 */
	initScripts: function() {
		this.container.select('a').each(function(el, index){
			this.scripts.push(new PROTO.Script(el));
			el._index = index;
		}.bind(this));
	},
	
	/** 
	 * Attaching onclick handler to container not links
	 * (as of speed issues and dynamic nature of inner links)
	 */
	initBehaviour: function() {
		this.ul.observe('click', this.onClick.bind(this))
	},
	
	onClick: function(e) {
		e.stop();
		this.selectScript(e.target.id);
	},
	
	/**
	 * Walks all the scripts in ScriptsContainer and updates visibility based on passed tags
	 * If script is associated with one of passed tags - it's visible, else hidden
	 * 
	 */
	update: function() {
		this.clearAll();
		var selectedTags = PROTO.tagCloud.getSelectedTags(),
			visibleScriptsNum = this.scripts.length;
		if (!selectedTags.length) {
			this.showAll(); // just showing everything if no tags were selected
			this.counter.update(this.scripts.length);
			return;
		}
		for (var i=0, iS=this.scripts, iL=iS.length, isIncluded=false; i<iL; i++) {
			iS[i].show();
			for (var j=0, jLen=selectedTags.length; j<jLen; j++) {
				for (var k=0, scriptTags=iS[i].tags, kLen=scriptTags.length; k<kLen; k++) {
					if (selectedTags[j] == scriptTags[k]) {
						isIncluded = true;
						break;
					}
					isIncluded = false;
				}
				if (!isIncluded) {
					break;
				}
			}
			if (!isIncluded) {
				iS[i].hide();
				visibleScriptsNum--;
			}
		}		
		// displaying number of visible scripts
		this.counter.update(visibleScriptsNum);
		
		// pluralize "script" => "scripts" properly
		this.counterWord.innerHTML = visibleScriptsNum == 1 ? 'script' : 'scripts';
		
	},
	
	showAll: function(){
		for (var i=0, s=this.scripts, l=s.length; i<l; i++) {
			s[i].show();
		}
	},
	
	clearAll: function() {
		for (var i=0, s=this.scripts, l=s.length; i<l; i++) {
			s[i].deselect();
		}
	},
	
	initScrolling: function() {
		this.container
			.observe('mousewheel', this.onScroll.bind(this))
			.observe('DOMMouseScroll', this.onScroll.bind(this));
	},
	
	onScroll: function(e) {
		e.stop();
		if (PROTO.slider.disabled) return;
		this[Event.wheel(e) > 0 ? 'scrollTop' : 'scrollBottom']();
	},
	
	scrollTop: function() {
		PROTO.slider.setValueBy(-0.0605);
	},
	
	scrollBottom: function() {
		PROTO.slider.setValueBy(0.0605);
		
	},
	
	scrollToSelected: function() {
		var total,
			top,
			percentage,
			ref = this.selectedScript.ref,
			container = this.container;
			 
		var positionSlider = function(scope) {
			total = scope.container.scrollHeight - scope.container.offsetHeight,
			top = total - scope.container.scrollTop,
			percentage = 1 - top/total;
			PROTO.slider.setValue(percentage);
		}
		if (this.selectedScript) {
			if (ref.offsetTop < container.scrollTop) {
				container.scrollTop -= (container.scrollTop - ref.offsetTop);
				positionSlider(this);
			}
			if ((ref.offsetTop - container.scrollTop + ref.getHeight()) > container.getHeight()) {
				container.scrollTop += ((ref.offsetTop - container.scrollTop) - container.getHeight() + ref.getHeight());
				positionSlider(this);
			}
		}
	},
	
	loadScript: function(id) {
		var script = this.find(id);
		
		// skip if data object is already present (json was already requested and stored)
		// we are checking for description since data object itself might already exist (for rating purposes)
		if (script.data && script.data.description) {
			this.displayScript(id);
		}
		// otherwise request json, eval it and store it in a data object
		else {
			new Ajax.Request(PROTO.config.path + 'json/get/script/' + script.ref.id, {
				onCreate: function() {
					script.ref.insert(new Element('img', {
						src: PROTO.LOADING_INDICATOR_URL,
						alt: 'Loading...',
						id: 'loading-indicator'
					}))
				},
				onSuccess: function(response) {
					$('loading-indicator').remove();
					script.data = response.responseJSON;
					if (script == this.selectedScript) {
						this.displayScript(script.id);
					}
				}.bind(this)
			})	
		}
	},
	
	displayScript: function(id) {
		
		var script = this.find(id),
			
			data = script.data,
			
			scriptAuthor = "",
			scriptURL = "",
			scriptRating = "",
			userScriptRating = "",
			userRatingDescription = "",
			averageRating = "",
			votesCount = "",
			votesCountWord = "",
			scriptImage = "",
			scriptLicense = "",
			
			authorInfo = "",
			licenseInfo = "";
			
			// Saving original name value (not truncated one)
			script.name = "";
			
		//new Effect.Highlight('scriptTitle', {duration: 0.75, startcolor: '#ffff99', queue: {scope: 'highlight', position: 'end', limit: 1}});
		
		//$('scriptTitle').down('span').update(script.name);
		//$('permalink').writeAttribute('href', '/script/' + script.formattedName);
		
		
		// updating link with retreived data if present, else creating it and then updating
		/*if (data.author_url) 
		{
		 if (scriptAuthor.down('a')) 
		 {
		  scriptAuthor.down('a').writeAttribute(authorInfo).update(data.author_name);
		 }
		 else 
		 {
		  scriptAuthor.update(new Element('a', authorInfo).update(data.author_name))
		 }
		}
		scriptAuthor.update(data.author_name);
		*/
		// quite ugly way to strip <br>'s from within <pre>
		// var rePre = /\<pre\>(.*)\<\/pre\>/;
		
		//data.description = data.description.replace(/\n/g, '<br>');
		
		// replacing nl's with br's
		//$('scriptDescription').update(data.description).select('pre br').each(Element.remove);
		//$('scriptLastModified').update(data.modified);
		
		//scriptURL
		//	.writeAttribute({ href: data.script_url, title: 'Visit ' + script.title })
		//	.update(script.name);
		
		/*if (data.license) {
			if (scriptLicense.down('a')) {
				scriptLicense.down('a').writeAttribute(licenseInfo).update(data.license);
			}
			else {
				scriptLicense.update(new Element('a',  licenseInfo).update(data.license))
			}
		}
		else {
		  scriptLicense.update(new Element('span').update('n/a'));
		}
		*/
		/*scriptImage.writeAttribute({
			src: data.img ? data.img : PROTO.EMPTY_IMAGE_URL,
			alt: script.name
		})*/
		
		//$('scriptSize').update(script.size + ' KB');
		//$('scriptTags').update(script.tags.join(', '));
		
		// stuffing rating info into appropriate containers to be used by rating widget later
		//if (!data.rating.avg)
		//  data.rating.avg = 0;
		//scriptRating.update(data.rating.avg);
		//averageRating.update(data.rating.avg);
		//votesCount.update(data.rating.cnt || 0);
		//votesCountWord.update(data.rating.cnt == 1 ? 'vote' : 'votes');
		
		// same for user rating widget if authorized
		/*if (PROTO.config.authorized) {
			userScriptRating.update(data.rating.rtn || 0);
			userRatingDescription.update(data.rating.dsc);
		}*/
		
	//	PROTO.main.initRatingControl();
	}
	
})

PROTO.AuxContainer = Class.create({
	
	initialize: function(container){
		this.container = $(container);
		this.initBehaviour();
	},
	
	initBehaviour: function() {
		this.container.observe('click', this.onClick.bind(this));
	},
	
	onClick: function(e) {
		e.stop();
		var target = e.target;
		if (target.tagName.toLowerCase() === 'a') {
			target.blur();
			PROTO.tagCloud.deselectAll();
			
			// selecting script from the left container by script id
			PROTO.allScripts.selectScript(target.id);
		}
	}
})

PROTO.main = {
	
	/**
	 * Searching document for tags => creating tags aray
	 * Indexing each tag (DOM element) with custom property to hold index
	 * 
	 * @return {Array} tags Array of tag objects
	 */
	initTags: function() {
		var tags = [];
		$$('#tagCloudContainer span a').each(function(el, index){
			el.parentNode._index = index;
			tags.push(new PROTO.Tag(el.innerHTML, el.parentNode));
		});
		return tags;
	},
	
	/**
	 * Binding click event hadlers to sort switch (bySize/byName)
	 * 
	 * @param {Object} container Container which sorting method will be binded to click events
	 */
	initSort: function(container) {
		$('sortByName', 'sortBySize').invoke('observe', 'click', function(e){
			e.stop();
			PROTO.utils.toggleSiblingsClassName(e.findElement('a'), 'selected');
			
			// these elements have same id's as sorting methods (sortByName/sortBySize)
			PROTO.allScripts[e.target.id]();
		})
	},
	
	initHistory: function() {
		dhtmlHistory.initialize();
		dhtmlHistory.addListener(PROTO.main.onHistoryChange);
	},
	
	onHistoryChange: function(id) {
		// skipping first time this handler is called by simply redefining it
		/*PROTO.main.onHistoryChange = function(id) {
			
		}*/
		if (id) {
			PROTO.allScripts.selectScript(id)
		}
		else history.back();
	},
	
	/**
	 * Switches visibility of popular/latest right containers
	 */
	initRightContainerTabs: function() {
		var popular = $('popular'), 
			latest = $('latest'), 
			id;
		
		$$('#leftTab, #rightTab').invoke('observe', 'click', function(e){
			
			e.stop();
			PROTO.utils.toggleSiblingsClassName(e.findElement('a'), 'active');
			id = e.target.parentNode.id;
			popular[id == 'leftTab' ? 'show' : 'hide']();
			latest[id == 'leftTab' ? 'hide' : 'show']();
		})
	},
	
	initRatingControl: function(value) {
		var selScript = PROTO.allScripts.selectedScript,
			ratingContainer = $('userScriptRating'),
			authContainer = $('authRequest');
		
		// setting main rating widget to display average value
		new Control.Rating('scriptRating', {value: selScript.data.rating.avg}).disable();
		
		// toggling user rating widget and "Please sign in..." message depending on user status (authorized or not)
		ratingContainer.up()[PROTO.config.authorized ? 'show' : 'hide']();
		authContainer[PROTO.config.authorized ? 'hide' : 'show']();
		
		if (!PROTO.config.authorized) return;
		
		// only initialize user rating if user is authorized
		var userRating = new Control.Rating(ratingContainer, {
			rated: false,
			afterChange: PROTO.main.onRate
		}).setValue(selScript.data.rating.rtn, false, true);
	},
	
	onRate: function(value) {
		
		var selScript = PROTO.allScripts.selectedScript;
		
		new Ajax.Request(PROTO.config.path + 'json/set/rating', {
			parameters: {nid: selScript.id, rating: value},
			onSuccess: function(resp) {
				var data = resp.responseJSON,
					  script = PROTO.allScripts.find(data.id),
					  o = script.data.rating;
				
				// refreshing script's rating data with received data
				Object.extend(o, data);
				
				// if received rating data is for currently selected script redraw widget
				if (selScript.id == data.id) {
					$('averageRating').update(o.avg || 0);
					$('votesCount').update(o.cnt || 0);
					$('votesCountWord').update(o.cnt == 1 ? 'vote' : 'votes');
					$('userRatingDescription').update(o.dsc);
					PROTO.main.initRatingControl();
				}
			}
		})
	},
	
	// We need this on initial page load when the json data was not yet populated into script object (script.data)
	// but we already need to initialize rating widget with this info
	// the solution is to scrape the page (with initial script data) and stuff it into appropriate fields of script.data.rating
	// keeping the entire process transparent for initRatingControl function
	
	// @param {Script} script to stuff rating data into
	prepareRating: function(script) {
		script.data = {}; var o =script.data.rating = {};
		o.rtn = $('userScriptRating').innerHTML;
		o.avg = $('averageRating').innerHTML;
		o.cnt = $('votesCount').innerHTML;
		o.dsc = $('userRatingDescription').innerHTML;
	},
	
	/**
	 * Prevents selection of elements with class unselectable
	 * This class is mostly attached to controls and script lists
	 * to prevent accidental text selection on click
	 * 
	 */
	initPreventSelect: function() {
		$$('.unselectable').invoke('makeUnselectable');
	},
	
	initLoginBehaviour: function() {
		var loginForm = $('loginForm');
		$('signIn').observe('click', function(e){
			e.stop();
			loginForm.toggle();
			if (loginForm.visible()) {
				loginForm.focusFirstElement();
			}
			else {
			  $('passwordField').setValue('');
			}
		})
	},
	
	initLogin: function() {
		var loginForm = $('loginForm'),
			  flash = $('loginFlash'),
			  errorMessage = PROTO.LOGIN_FAILED_MSG,
			  emptyFieldsMessage = PROTO.EMPTY_FIELDS_MSG;
		
		loginForm.observe('submit', function(e){
			e.stop();
			
			var name = $F('loginField'),
			    indicator = $('loginBusy'),
			    button = $('signInBtn');
			    
			if (this.isEmpty()) {
				
				button.value = '';
				indicator.show();
				
				new Ajax.Request(PROTO.config.path + 'json/set/login', {
					method: 'POST',
					parameters: $(this).serialize(),
					onSuccess: function(resp){
						var data = resp.responseJSON;
						
						indicator.hide();
						button.value = 'Sign in';
						
						if (data.messages == 'failed') {
							flash.center({height: false}).flash(errorMessage, 'failure');
						}
						else {
							if (!PROTO.config.authorized) {
								new Effect.Opacity('loginForm', {
									to: 0,
									duration: 0.5,
									afterFinish: function() {
										$('loginForm').hide().setOpacity(1);
									}
								});
								PROTO.config.authorized = true;
								PROTO.main.redrawScreen();
							}
						}
					}
				})
			}
			else {
				flash.center({height: false}).flash(emptyFieldsMessage, 'failure');
			}
		})
	},
	
	initLogout: function() {
		$('signOut').observe('click', function(e){
			e.stop();
			new Ajax.Request(PROTO.config.path + 'json/set/logout');
			PROTO.config.authorized = false;
			PROTO.main.redrawScreen();
		})
	},
	
	redrawScreen: function() {
		var auth = PROTO.config.authorized,
			  message = auth ? 'You are now signed in' : 'You have just signed out',
			  status = auth ? 'success' : 'failure',
			  username = auth ? $('loginField').value : '';
		
		$('loggedInUserName').update(username);
		$('signOutContainer')[auth ? 'show' : 'hide']();
		$('signInContainer')[auth ? 'hide' : 'show']();
		$('claimScript')[auth ? 'show' : 'hide']();
		
		PROTO.main.initRatingControl();
		
		$('passwordField').setValue('');
		
		$('loginFlash').center({height: false}).flash(message, status);
	},
	
	initSlider: function() {
		var handler = $('handler'),
			  leftContainer = $('leftContainerMid'),
			  diff = leftContainer.scrollHeight - leftContainer.offsetHeight;

		function scrollVertical(value) {
			leftContainer.scrollTop = Math.round(value * diff);
		}
		PROTO.slider = new Control.Slider('handler', 'scrollbar', {
			axis: 'vertical',
			onSlide: scrollVertical,
			onChange: scrollVertical,
			onStartDrag: function(){
				handler.setOpacity(0.5);
			},
			onFinishDrag: function(){
				handler.setOpacity(1);
			}
		});
		PROTO.slider.setValue(0);
	},
	
	initRegister: function() {
		var formContainer = $('register-form-container'),
			overlay = $('body-overlay'),
			loginFlash = $('loginFlash');
		
		overlay.fillBody().setOpacity(0.6);
		
		$('register').observe('click', function(e){
			e.stop();
			overlay.show();
			formContainer.show().center().down('form').focusFirstElement();
		});
		
		PROTO.main.initRegisterSubmit();
		
		formContainer.down('a.cancel').observe('click', function(e){
			e.stop();
			overlay.hide();
			formContainer.hide();
		});
	},
	
	initRegisterSubmit: function() {
		var container = $('registerFlash').down('p');
		
		$('register-form').observe('submit', function(e){
			e.stop();	
			if (this.getInputs().any(function(el){return $F(el).empty()})) {
				container.flash('Please specify both username and email', 'failure');
			}
			else {
			  
			  $('submitBtn').value = '';
			  $('registerBusy').show();
				
				new Ajax.Request(PROTO.config.path + 'json/set/register', {
					method: 'POST',
					parameters: $(this).serialize(),
					onSuccess: function(resp){
			      $('registerBusy').hide();
			      $('submitBtn').value = 'Register';
						
						var data = resp.responseJSON;
						if (data.messages) {
							container.flash($H(data.messages).values().first().stripTags(), 'failure');
						}
						else {
							container.flash('Check your email to confirm registration.', 'success');
						}
					}
				})
			}
		})
	},
	
	initClaimScript: function() {
		$('claimScript').observe('click', function(e){
			e.stop();
			if (!PROTO.config.authorized) return;
			if (!confirm('Are you sure you wish to claim this script as yours?')) return;
			new Ajax.Request(PROTO.config.path + 'json/set/claim', {
				parameters: {id: PROTO.allScripts.selectedScript.id},
				onComplete: function() {
					$('loginFlash').center({height: false}).flash('Your claim has been submitted for review', 'success');
				}
			})
		})
	},
	
	initAbout: function() {
	  var popup = $('about-container');
	  
	  $('aboutTab').observe('click', function(e){
	    e.stop();
	    PROTO.overlay.show();
	    popup.show().center();
	  })
	  
	  $('closeAbout').observe('click', function(e){
	    e.stop();
	    PROTO.overlay.hide();
	    popup.hide();
	  })
	},
	
	// popups are closed in an order based on z-index: higher ones - first
	// no actual checking occurs, instead we construct array in such way ourselves
	// see PROTO.popups
	initKeyEvents: function() {
	  document.observe('keyup', function(e){
			if (e.keyCode == Event.KEY_ESC) {
				var visiblePopup = PROTO.popups.find(Element.visible);
				if (visiblePopup) {
				  PROTO.overlay.hide();
				  if (visiblePopup == $('loginForm'))
				    // clearing password field of a login form on close
				    $('passwordField').setValue('');
				  visiblePopup.hide();
				}
			}
		})
	},
	
	// any currently opened popups will be resized/repositioned 
	// once window is resized/scrolled
	initWindowEvents: function() {
	  ['scroll', 'resize'].each(function(ev){
			Event.observe(window, ev, function(){
				var visiblePopup = PROTO.popups.find(Element.visible),
				    loginFlash = $('loginFlash');
				    
				if (visiblePopup && visiblePopup !== $('loginForm')) {
					PROTO.overlay.fillBody();
					visiblePopup.center();
				}
				if (loginFlash.visible()) {
					loginFlash.center({height: false});
				}
			})
		})
	},
	
	cacheImages: function() {
		PROTO.IMAGES_TO_CACHE.each(function(img){
			PROTO.cachedImages.push(new Image().src = PROTO.config.images + img);
		})
	}
}

PROTO.init = function() {
	
	var Pm = PROTO.main;
	
	//PROTO.EMPTY_IMAGE_URL = PROTO.config.images + PROTO.EMPTY_IMAGE_URL;
	//PROTO.LOADING_INDICATOR_URL = PROTO.config.images + PROTO.LOADING_INDICATOR_URL;
	
	PROTO.overlay = $('body-overlay');
	PROTO.popups = $$('#register-form-container, #about-container, #loginForm');
	
	// Opera for some reason does not react to backgroundPosition change 
	// if element dimensions are not affected during mouseover
	// therefore this ugly hack (increasing width by 1px)
	var head = $$('head')[0];
	
	if (Prototype.Browser.Opera) {
	  head.insert('<style type="text/css">div.ratingControl a.rating_selected {width: 16px}</style>');
	}

	Pm.initSlider();
	
	PROTO.allScripts = new PROTO.ScriptsContainer('leftContainerMid');
	PROTO.popularScripts = new PROTO.AuxContainer('popular');
	PROTO.latestScripts = new PROTO.AuxContainer('latest');

	// Initializing tags => passing them to tagCloud
	PROTO.tagCloud = new PROTO.TagCloud(PROTO.main.initTags()); 
	
	var scriptId = $$('div.contentWrapper')[0].id,
		  script = PROTO.allScripts.find(PROTO.utils.parseId(scriptId));
	
	// selecting script with "_static" option set to true to prevent json request
	PROTO.allScripts.selectScript(scriptId, true);
	
	Pm.prepareRating(script);
	Pm.initRatingControl();
	
	// Observing 'tagCloud:updated' event (see TagCloud class for methods which fire this event)
	document.observe('tagCloud:updated', function(){
		PROTO.allScripts.update();
		PROTO.utils.toggleScrollbar();
	})
	
	$('permalink').writeAttribute('href', '/script/' + script.formattedName);
	
	Pm.cacheImages();
	
	Pm.initSort(PROTO.allScripts);
	Pm.initKeyEvents();
	Pm.initPreventSelect();
	Pm.initLoginBehaviour();
	Pm.initLogin();
	Pm.initLogout();
	Pm.initRegister();
	Pm.initAbout();
	Pm.initRightContainerTabs();
	Pm.initClaimScript();
	Pm.initWindowEvents();
	
}

// and let the magic begin
document.observe('dom:loaded', PROTO.init);