/* LiveWhale Common Frontend and Backend Effects */
/* (requires jQuery 1.3.1, jQuery UI, swfUpload) */
/* by White Whale Web Services */

// To avoid accidental errors in browsers without a console during debugging
if(!window.console) window.console = { log:function(){},warn:function(){},error:function(){} };

if(!window.livewhale) { // if the livewhale namespace doesn't exist
	livewhale = {}; // create the livewhale object
}

if(!window.lw_callbacks) { // if the lw_callbacks namespace doesn't exist
	lw_callbacks = {}; // create it
}

livewhale.jQuery = jQuery;

(function($){
$.ajaxSetup({ // set default settings for Ajax requests
	timeout:5000, // 5 second time out before reporting an error,
	error:function(XMLHttpRequest, textStatus, errorThrown) { // on error
		switch(textStatus) {
			case 'timeout': // request timed out
				message = 'Your request has taken too long to complete. Please reload the page and try again.';
				break;
			case 'parsererror': // wrong datatype (e.g. xml, JSON...)
				message = 'Sorry, the returned content does not match the expected format: <em>'+XMLHttpRequest.responseText.replace(/</g,'&lt;').substr(0,500)+'</em>...';
				break;
			case 'error': // HTTP error
				message = 'The server returned the status error <em>'+XMLHttpRequest.status+'</em>.'; // otherwise, report the status error
				break;
			default:
				message = 'Please <a href="mailto:livewhale@whitewhale.net">let us know</a>!'; // if the error is an unknown type, prompt for a report
				break;
		}
		$('.lw_spinner').remove(); // remove any currently active spinners
		var previous_error = $('.lw_fatalerror').eq(0), // grab the previous error
			error = $('<div class="lw_element lw_fatalerror"><div>There was an error loading the URL <em>'+this.url+'</em>: '+message+'</div></div>').prependTo('body') // add the error
					.css('top',previous_error.length ? (previous_error.offset().top+previous_error.outerHeight()-1)-document.documentElement.scrollTop+'px' : '-1px'); // and move it right below the existing ones
		setTimeout(function() { // after five seconds
			error.animate({opacity:0},750,'easeInOutSine',function() { // fade out the element
				$('.lw_fatalerror').animate({top:'-='+$(this).outerHeight()+'px'},{duration:500,queue:false,easing:'easeInOutSine'}); // and slide up the rest
				$(this).remove();
			});
		},10000);
		return false;
	}
});

// Add LiveWhale-specific jQuery plugins
$.fn.extend({
	delegate: function(event,actions) { // basic event delegation
		var self = this;
		this.bind(event,function(e) { // bind the specified event
			$.each(actions,function(selector,action) { // for each possible action
				var matches = self.find(selector); // find all the possible matches
				if(matches.length) { // if there are any possible matches
					var targets = $(e.target); // get the lowest level target
					var parents = targets.parents(); // and its parents
					var index = parents.index(self); // find the index of the current element
					if(index>-1) targets = targets.add(parents.slice(0,index)); // so that, if there are any, we can add only those parents which are in the current scope
					$.each(targets,function() { // with each target
						if(matches.index(this)>-1) { // if the target is a match
							if(action.apply(this,[e])==false) // fire the action. If the function returns false,
								e.preventDefault(); // prevent the default action
						}
					});
				}
			});
		});
		return this;
	},
	overlay: function(options) { // adds the specified element as an overlay on top of the rest of the document (see below for options/commands)
		var self = this;
		options = options || {};
		if(typeof options == 'object') { // if options is unset or the options object, we create a new overlay
			s = {
				closers:options.closers || '.lw_close_overlay', // CSS selector for elements which should destroy the overlay onclick
				allow_close:options.allow_close || true
			};
			var overlays = $('.lw_overlay_container'); // grab current overlays
			var id = overlays.length; // the ID of this overlay will be the next integer
			this.data('lw_overlay',{ // store the overlay data
				id:id,
				callback:options.callback || function() {}, // callback upon destruction
				margin:options.margin || 20 // the top/bottom offset around the element
			});
			$('body').prepend('<div id="lw_overlay'+id+'"><div class="lw_blackout" style="z-index:'+(12000+(100*id))+';"/><div class="lw_overlay_container" style="z-index:'+(12010+(100*id))+';"/></div>'); // add a blackout and a container for this overlay (to help position it horizontally) (use a z-index higher than any extant overlays)
			if(s.allow_close) {
				s.closers+=(',.lw_overlay_close');
				$('#lw_overlay'+id).prepend('<a href="#" class="lw_overlay_close" style="z-index:'+(12020+(100*id))+';"><img src="?livewhale=image&f=overlay_close.png" alt="Cancel and close"/></a>');
			}
			var container = $('#lw_overlay'+id+' .lw_overlay_container'); // get the overlay container
			container.append(self); // add overlay element to container
			self.overlay('position'); // position the overlay
			$('#lw_overlay'+id).find(s.closers).click(function() { self.overlay('destroy'); return false; }); // add action for closer selectors
		} else { // otherwise, the user has sent a command
			var overlay = self.parent().parent(); // grab the entire overlay element
			switch(options) {
				case 'show': // showing a previously hidden overlay
					overlay.show();
					break;
				case 'hide': // hide the overlay
					overlay.hide();
					break;
				case 'position': // update  the overlay's position
					var s = $(this).data('lw_overlay');
					var height = self.outerHeight(); // get height of overlay element
					var viewHeight = $(window).height(); // get window height
					if (height<viewHeight) { // if the element fits on the screen
						margin = Math.floor((viewHeight-height)/2); // center it vertically
					} else {
						margin = s.margin; // otherwise, use the specified margin
					}
					$('#lw_overlay'+s.id+' .lw_overlay_container').css('top',(document.documentElement.scrollTop+margin)+'px'); // place the overlay based on the margin and the current viewport position
					break;
				case 'destroy': // destroy the overlay
					self.data('lw_overlay').callback(); // apply the callback
					overlay.remove();
					return; // return nothing since the overlay no longer exists
			}
		}
		return this;
	},
	reveal: function(margin,duration,callback) { // scroll the browser to fit this element in the viewport (margin determines the top/bottom offset around the element)
		var self = this;
		var elem = { height:this.outerHeight(), top:this.offset().top };
		var viewport = { height:$(window).height(), top:document.documentElement.scrollTop };
		margin = margin || 20;
		duration = duration || 'normal';
		callback = callback || function(){};
		if($.isFunction(duration)) { // if params is a function
			callback = duration; // assume it's the callback
			duration = null;
		}
		elem.bottom = elem.top + elem.height;
		viewport.bottom = viewport.top + viewport.height;
		if (viewport.height-elem.height<margin) { // if the margin would force an element edge out of the viewport
			margin = (viewport.height-elem.height<=0 ? 0 : Math.floor((viewport.height-elem.height)/2)); // center the element vertically instead of using specified margin
		}
		if(viewport.top<=elem.top&&viewport.bottom>=elem.bottom) { // if we don't need to scroll
			callback.apply(self); // just call the callback
		} else if(viewport.top>elem.top || elem.height>=viewport.height) { // if the element is above the viewport, or the element is taller than the viewport scroll to the top of it
			$('html').animate({scrollTop:(elem.top-margin)},duration,'easeInOutSine',function() { callback.apply(self); });  // scroll until the element is (margin) pixels from the top
		} else { // otherwise, we need to scroll down, with the margin
			$('html').animate({scrollTop:elem.bottom+margin-viewport.height},duration,'easeInOutSine',function() { callback.apply(self); }); // scroll until the element is (margin) pixels from the bottom
		}
		return this; 
	},
	imageLibrary: function(options,images) { // load an image library import panel
		var self = this;
		options = options || function() {}; // callback to perform when an image is toggled (args are the id of the toggled element and a boolean for whether it was selected or deselected)
		images = images || [];
		if(typeof options == 'object') { // if toggle is an object (or was not set)
			var s = {
					toggle : options.toggle || function() {}, // toggle callback
					multiple : typeof options.multiple=='boolean' ? options.multiple : true, // allow selection of multiple images?
					preselect : options.preselect || [] // any images that should be pre-selected
				},
				selected = {},
				toggleImage = function(image) { // when toggling an image
					var row = $('#lw_images_results #lw_results_image_'+image.id); // the image result row
					if(selected[image.id]) { // if it is selected
						delete selected[image.id]; // deselect
						row.removeClass('lw_selected').find('input:checkbox').removeAttr('checked'); // and unhighlight it
					} else { // otherwise
						if(!s.multiple) { // if only one item is selectable
							selected = {}; // deselect the others
							row.siblings().removeClass('lw_selected').find('input:checkbox').removeAttr('checked'); // and unhighlight them
						}
						selected[image.id]=true; // select it
						row.addClass('lw_selected').find('input:checkbox').attr('checked','checked');; // and highlight it	
					}
					self.data('selected',selected);
					s.toggle.apply(self,[image,selected[image.id]==true]); // and perform the toggle callback
				};
			self.data('toggle',toggleImage);
			if(s.preselect.length) { // if preselecting images
				$.each(s.preselect,function(key,id) { // for each
					selected[id] = true; // add it to the selected set
				});
				self.data('selected',selected); // and save the set
			}
			this.append('<div id="lw_images_library"><div id="lw_images_search"><label for="lw_images_search_query">Find images by keyword</label><input type="text" id="lw_images_search_query"/></div><div id="lw_images_results"><div class="lw_spinner"/></div><div id="lw_images_upload" class="lw_panel_content">or <a href="#" class="lw_openoverlay" id="lw_link_library_uploads">Add new images from your computer</a></div>').reveal(); // add all the content and scroll to it
			$('#lw_images_results .lw_keywords a').die().live('click',function() { // perform keyword search
				 $('#lw_images_search_query').val($(this).text()).focus().keyup();
				return false;
			});
			$('#lw_images_results a:has(img)').die().live('click',function() { // preview image
				livewhale.previewImage($(this).parents('li').eq(0).find('a:has(img)').attr('href'));
				return false;
			});
			$('#lw_images_results li').die().live('click',function(e) { // image toggle
				if(!$(e.target).is('a,img')) { // if we're not clicking a link or an image
					var image = {};
					$(this).find(':input').each(function() { // find each input
						image[this.name]=$(this).val(); // and add it to the image object
					});
					toggleImage(image);
				}
			});
			$('#lw_images_search_query').focus().keyup(function() { // when typing in the search box
				var query = $(this).val(); // store the query
				$('#lw_images_results').load(livewhale.livewhale_dir+'/?livewhale=html&template=images_attachment'+(query ? '&search='+query.replace(/\s/,'%20') : ''),function() { // load the search results
					$.each(selected,function(key,value) { // once those are loaded, we'll iterate through the selected photos there are probably fewer of them than results					
						$('#lw_images_results li#lw_results_image_'+key).addClass('lw_selected').find('input').attr('checked','true'); // add 'selected' class and check the checkbox for each					
					});
				});
			}).keyup();
			$('#lw_link_library_uploads').swfuploadInline({ // attach uploading behavior
				upload_url:livewhale.livewhale_dir+'/?livewhale=swfupload&type=images',
				file_size_limit : livewhale.upload_max_filesize+' MB', 
				file_types : '*.jpg;*.jpeg;*.png;*.gif',
				file_types_description : 'Image Files',
				multiple:true,
				disable_elements:'#lw_uploads_batch_save',
				callback_queued: function() { // on queue, fix generic upload messages
					$('#lw_uploads_batch label').html('Add Images to Library');
					$('#lw_uploads_batch_save').data('upload_val','Add images to library');
				},
				callback_success: function(file,data) { // on success, prompt for file info
					$('#lw_'+file.id).after('<div class="lw_uploads_image" id="lw_'+file.id+'_details"><a href="'+livewhale.livewhale_dir+'/?livewhale=swfupload_preview&size=preview_lg&filename='+data.preview+'"><img src="'+livewhale.livewhale_dir+'/?livewhale=swfupload_preview&size=thumb&filename='+data.preview+'"/></a><input type="hidden" name="uploads[]" value="'+data.upload+'"/><input type="text" name="descriptions[]" value="'+file.name.substring(0,file.name.lastIndexOf('.'))+'"/><textarea name="keywords[]"></textarea></div>');
					$('#lw_'+file.id+'_details').find('textarea').inlineLabel('Enter a comma-separated list of keywords for this image...');
				},
				callback_complete: function() { // once complete
					$('#lw_uploads_batch_save').click(function() { // set save action
						var images = new Array();
						$('#lw_uploads_batch').append('<div class="lw_spinner"/>')
							.find('.lw_inline_label')
								.removeClass('lw_inline_label')
								.val('');
						$.ajax({
							type:'post',
							url:livewhale.livewhale_dir+'/?livewhale=swfupload_bulk&type=images',
							data:$('#lw_uploads_batch :input').serialize(),
							success:function(data,textStatus) {
								$.each(data,function() { // with selected images
									toggleImage(this); // toggle it (to select it)
								});
								$('#lw_images_search_query').keyup();
								$('#lw_images_results')[0].scrollTop = 0;
								$('#lw_uploads_batch').overlay('destroy');
							},
							timeout:30000,
							dataType:'json'
						});
					});
				}
			});
		} else { // otherwise, we're performing a command
			var selected = self.data('selected') || []; // get any already selected images
			if(options=='toggle'&&images) { // otherwise, if we're toggling specific images
				if(typeof images != 'object') { // if only a single image
					self.data('toggle')({id:images}); // toggle it
				} else { // otherwise
					$.each(images,function(key,id) { // for each
						self.data('toggle')({id:id}); // toggle it
					});
				}
			}
		}
		return this;
	},
	quickGallery: function() { // make this <ul> into a basic gallery
		var self = this;
		var gallery = self.wrap('<div class="lw_quickgallery"/>') // wrap the quick gallery div around it
			.parent() // select it
			.append('<div class="lw_spinner"/>'); // and append the spinner		
		var photos = self.children();// grab all the <li>s
		var count = 0;
		var total = photos.length;
		photos.each(function() { 
			count++;
			$(this).append('<div class="lw_quickgallery_count">Image '+count+' of '+total+'</div>'); // and append a count to each
		});
		self.loadImages(function() {  // once all the images are loaded
			gallery.css({height:photos.eq(0).outerHeight({margin:true})+'px'}) // give the gallery an initial height/width			
				.find('.lw_spinner') // then find the spinner
				.remove(); // and remove it
		});
		if(total>1) { // if there's more than one photo
			var prevlink = $('<a class="lw_quickgallery_prev" href="#">&laquo; Previous</a>').appendTo(gallery) // add the 'Previous' link
				.click(function() { // and attach its click event
					var orig = self.find('.lw_quickgallery_selected');
					var prev = orig.prev(); // grab the "prev" photo
					prev.css('z-index',500) // bump up its z-index
						.fadeIn(500,function() {// and show it
							$(this).addClass('lw_quickgallery_selected') // add the selected marker
								.find('textarea') // find its caption
									.focus(); // and focus in it
							if(!$.browser.msie||$.browser.version>7) $(this).css('z-index','auto') // reset its z-index; breaks IE<=7
							orig.removeClass('lw_quickgallery_selected') // remove the selected marker from the prior photo
								.hide(); // and hide it
							nextlink.show(); // show the 'next' link
							if(!$(this).prev().length) prevlink.hide(); // hide the 'previous' link if necessary
							else prevlink.show(); // otherwise, make sure it's showing
						});
					gallery.animate({height:prev.outerHeight({margin:true})+'px'},500,'easeInOutSine'); // meanwhile, animate the gallery height			
					return false;
				}).hide(); // finally, hide it (since we're showing the first image)
			var nextlink = $('<a class="lw_quickgallery_next" href="#">Next &raquo;</a>').appendTo(gallery) // add the 'Next' link
				.click(function() { // attach the click event
					var orig = self.find('.lw_quickgallery_selected');
					var next = orig.next(); // grab the "next" photo
					next.css('z-index',500) // bump up its z-index
						.fadeIn(500,function() { // and show it
							$(this).addClass('lw_quickgallery_selected') // add the selected marker
								.find('textarea') // find its caption
									.focus(); // and focus in it
							if(!$.browser.msie||$.browser.version>7) $(this).css('z-index','auto') // reset its z-index
							orig.removeClass('lw_quickgallery_selected') // remove the selected marker from the prior photo
								.hide(); // and hide it
							prevlink.show(); // show the 'previous' link
							if(!$(this).next().length) nextlink.hide(); // hide the 'next' link if necessary
							else nextlink.show(); // otherwise, make sure it's showing
						});
				gallery.animate({height:next.outerHeight({margin:true})+'px'},500,'easeInOutSine'); // meanwhile, animate the gallery height	
				return false;
			});
		} else {
			$('.lw_quickgallery_count').hide();
		}
		photos.eq(0) // select the first photo
			.addClass('lw_quickgallery_selected') // and mark it as selected
			.siblings() // grab the rest of the photos
				.hide(); // and hide them
		return this;
	},
	loadAs: function(url,params,callback) { // replaces an element's content with an Ajax response minus the root node, with spinner animation and height/width animation; use like .load()
		var self = this;
		params = params || null; // set the original params
		callback = callback || function(){}; // set the original callback
		if($.isFunction(params)) { // if params is a function
			callback = params; // assume it's the callback
			params = null;
		}
		var clone = self.clone() // create a new element just like this one
			.load(url,params,function(responseText, textStatus, XMLHttpRequest) { // load() the contents into it
				var contents = $(responseText).html(); // get everything except the root node
				self.html(contents); // set the contents
				callback.apply(self,[responseText, textStatus, XMLHttpRequest]); // and finally run the callback
			});
		return this;
	},
	loadImages: function(callback) { // duplicates the load event (for images in this container), but will fire even if the images are already loaded
		var self = this;
		var images = self.find('img'); // get images
		var count = 0;
		var total = images.length; // and the total number of images
		$.each(images,function() { // with each image
			$('<img src="'+$(this).attr('src')+'"/>') // load the image as a string
				.load(function() { // and once its loaded
					count++;
					if(count==total) callback.apply(self); // if all have loaded, fire the callback
				});
		});
	},
	outerHTML: function() { // get an element's HTML, including the element itself
		return $('<div/>').append(this.clone()).html();
	},
	inlineLabel: function(text,style) {
		if(typeof style !='string') { // if no style is specified (including empty strings)
			style ='lw_inline_label'; // the default CSS class for placeholder text	
		}
		this.blur(function() { // onblur
			var val = $.trim($(this).val());
			if(!val||val==text) { // if this input has no contents or the contents are identical to the placeholder text
				$(this).addClass(style) // add the inline_label class
					.val(text) // set the appropriate text
					.one('focus',function() { // and, on the first focus
						$(this).val('') // remove that text
							.removeClass(style); // and the inline_label class
					});
			}
		}).blur(); // and do all this right now
		return this; // return original element for chaining
	}
});

$.fn._wrapInner = $.fn.wrapInner;
$.fn.wrapInner = function(wrapper){
	if(this.contents().length) return this._wrapInner(wrapper);
	else return this.append(wrapper);
}

livewhale.previewImage = function(url) { // preview an image, lightbox style
	$('<div id="lw_image_preview" class="lw_overlay"><div class="lw_spinner"/></div>').overlay({closers:'.lw_blackout,.lw_overlay_container'}); // create a new overlay with a loading spinner; clicks anywhere will close it
	$('<img src="'+url+'" alt="Image Preview"/>').load(function() { // wait for the image to load
		var preview = $('#lw_image_preview').prepend(this); // place image in preview div and store the div
		var width = $(this).width(), height=$(this).height(); // get the image's dimensions
		preview.animate({width:width+'px',height:height+'px'},750,'easeInOutSine',function() { $(this).children('.lw_spinner').remove(); }) // resize preview div, then remove the spinner
			.parent().animate({top:'-='+((height-200)/2)+'px'},750,'easeInOutSine'); // meanwhile, move the whole overlay to keep the image approximately centered on the screen
	});	
	return false;
}

livewhale.getCookie = function(name) {
	var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
		for (var i = 0; i < cookies.length; i++) {
			var cookie = $.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

livewhale.setCookie = function(name,value,expires,path,domain,secure) {
var today = new Date();
today.setTime(today.getTime());
var expires_date = new Date(today.getTime() + (expires*1000));
document.cookie = name + "=" +escape(value) + ((expires) ? ";expires=" + expires_date.toGMTString() : "") + ((path) ? ";path=" + path : "") + ((domain) ? ";domain=" + domain : "") +((secure) ? ";secure" : "");
}

function getVar(name) {
         get_string = document.location.search;         
         return_value = '';
         do { //This loop is made to catch all instances of any get variable.
            name_index = get_string.indexOf(name + '=');
            if(name_index != -1)
              {
              get_string = get_string.substr(name_index + name.length + 1, get_string.length - name_index);
              end_of_value = get_string.indexOf('&');
              if(end_of_value != -1)                
                value = get_string.substr(0, end_of_value);                
              else                
                value = get_string;                
              if(return_value == '' || value == '')
                 return_value += value;
              else
                 return_value += ', ' + value;
              }
            } while(name_index != -1)
         //Restores all the blank spaces.
         space = return_value.indexOf('+');
         while(space != -1)
              { 
              return_value = return_value.substr(0, space) + ' ' + 
              return_value.substr(space + 1, return_value.length);
							 
              space = return_value.indexOf('+');
              }
         return(return_value);        
}

})(livewhale.jQuery);