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

// Return jQuery to its original settings
livewhale.jQuery = jQuery;
if(livewhale.jQuery_before&&livewhale.jQuery_before.jQuery) {
	jQuery = livewhale.jQuery_before.jQuery;
	$ = livewhale.jQuery_before.$; 
}

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

(function($){

$.ajaxSetup({ // set default settings for Ajax requests
	cache:true // force cache to true, even in the case of Javascript and JSONP requests
});

if(!livewhale.callbacks) livewhale.callbacks = {} // initialize callbacks
$.each(['Paginate'],function() { // for each function
	if(!livewhale.callbacks['before'+this]) livewhale.callbacks['before'+this] = function() { return true; }; // set before
	if(!livewhale.callbacks['on'+this]) livewhale.callbacks['on'+this] = function() { return true; }; // and after
});

livewhale.prompt = function(message,type,options) { // type is 'success','failure','warning'; options is an object with 'Title':callback pairs
	if(typeof type=='object') { // if type is an object
		options = type; // treat it as options
		type = false; // and remove the type
	}
	options = options || {'Okay':null}; // default option is "Okay" with no callback
	var prompt = $('<div>'+message+'</div>'), // create the prompt
		prompt_options = $('<div class="lw_prompt_options"></div>').appendTo(prompt); // and the prompt's buttons
	$.each(options,function(title,callback) {
		var button;
		if(title=='Cancel') {  // 'Cancel' is a special-case
			button = $('<a href="#">cancel</a>');
			button.appendTo($('<span class="lw_cancel">or </span>').appendTo(prompt_options));
		} else {
			button = $('<button>'+title+'</button>').appendTo(prompt_options);
		}
		button.click(function() {
			if(callback) callback.apply(prompt); // run any callback with the prompt as context
			prompt.overlay('remove'); // and kill the overlay
			return false;
		});
	});
	prompt.overlay({style:'lw_prompt'+(type ? ' lw_msg_'+type : ''),close_button:false}); // finally, show the prompt
}

// Add LiveWhale-specific jQuery plugins
$.fn.extend({
	overlay: function(options) { // adds the specified element as an overlay on top of the rest of the document (see below for options/commands)
		var args = arguments;
		options = options || {};
		this.each(function() {
			var self = $(this);
			if(typeof options == 'object') { // if options is unset or the options object, we create a new overlay
				var s = {
					id: options.id || false, // ID for the overlay content element
					style: options.style || false, // additional classes for the overlay content element
					close: (options.close ? options.close+',' : '')+'.lw_overlay_close,.lw_overlay_close_button', // CSS selector for elements which should destroy the overlay onclick
					close_button: (options.close_button===false ? false : true), // show a close button?
					fadeIn: (typeof options.fadeIn=='undefined' ? 300 : options.fadeIn || 0), // fade the overlay in (ms)
					complete: options.complete || function() { } // when the overlay completes
				};
				self.data('overlay',s); // save settings
				var container = $('<div class="lw_element lw_overlay_container"><div class="lw_overlay_blackout"/><div class="lw_overlay'+(s.style ? ' '+s.style : '')+'"'+(s.id ? ' id="'+s.id+'"' : '')+'><div class="lw_overlay_contents"/>'+(s.close_button ? '<a class="lw_overlay_close_button" href="#">&times;</a>' : '')+'</div></div>').appendTo('body'), // append the overlay (hidden)
					contents = container.find('.lw_overlay_contents');
				contents.append(self); // add the content
				container.delegate(s.close,'click',function() { // delegate closing the overlay
					self.overlay('remove'); // remove it
					return false; // and cancel the click
				});
				self.overlay('position'); // and position the overlay
				container.find('.lw_overlay_blackout').fadeTo(s.fadeIn/2,1,function() { // show the overlay blackout
					container.find('.lw_overlay').fadeTo(s.fadeIn/2,1, s.complete() ); // and the overlay itself
				});
			} else { // otherwise, the user has sent a command
				var container = self.parents('.lw_overlay_container'), // grab the entire overlay element
					contents = container.find('.lw_overlay_contents'); // and the content div
				switch(options) {
					case 'html':
						contents.html(args[1]); // set HTML
						self.overlay('position'); // and position the overlay
						break;
					case 'position': // update the overlay's position
						var overlay = contents.parent().removeAttr('style'), // clear the height and width
							offset = args[1] || Math.max(($(window).height()-overlay.outerHeight())/4,10); // if there's no offset specified, set the overlay near the top of the window
						overlay.css({top:$(document).scrollTop()+offset});
						overlay.width(overlay.width()); // and fix its width if not fixed
						break;
					case 'sizeTo':
						var width = args[1],
							height = args[2],
							callback = args[3] || function() { },
							overlay = contents.parent(),
							offset = Math.max(overlay.offset().top-(height-overlay.height())/2,$(document).scrollTop()+10);
						overlay.animate({width:width,height:height,top:offset},750,function() {
							callback.apply(self);
						});
						break;						
					case 'remove': // destroy the overlay
						var s = self.data('overlay');
						container.focusout().fadeTo(s.fadeIn/2,0,function() { // and the overlay itself
							container.remove(); // remove the overlay
						});
						return; // return nothing since the overlay no longer exists
						break;
				}
			}
		});
		return this; // return original element for chaining
	},
	notify: function(options,type,callback) { // (message/options, type, callback), (message,callback), (options,callback), etc, pops up a notification bar, attached to the specified element
		var self = this,
			s = {
				id:options.id || false, // id for the notice itself; multiple notices of this ID will replace each other
				message: options.message || options, // the text of the notification
				details: options.details || false, // message details
				type: (typeof type=='string' ? type : options.type || false), // the type of notification (success, failure, warning)
				slideIn: (typeof options.slideIn=='undefined' ? 150 : options.slideIn || 0), // the time to run the slide animation
				duration: options.duration || false, // hide the notification after duration MS
				close_button:(options.close_button===false ? false : true), // show a close button?
				callback: (typeof type=='function' ? type : options.callback || function(first) {}), // callback once the notice is attached, with the notice as the context
				log:(options.log===false ? false : true) // offer a log of the combined notices?
			},
			notice = $('<div class="lw_notice '+(s.type ? ' lw_msg_'+s.type : '')+'"'+(s.id ? ' id="'+s.id+'"' : '')+'><div class="lw_container"><a class="lw_notice_close_button" href="#"'+(s.close_button ? '' : ' style="display:none;"')+'>&times;</a>'+s.message+(s.details ? '<div class="lw_notice_details">'+s.details+'</div>' : '')+'<a href="#" class="lw_notice_showdetails"'+(s.details ? '' : ' style="display:none;"')+'>More...</a></div></div>'),
			close = notice.find('.lw_notice_close_button').click(function() { // when clicking the close button
				notice.slideUp(150,function() { notice.remove() }); // slide up the notice and remove it
				return false; // cancel the original click
			}),
			showdetails = notice.find('.lw_notice_showdetails').click(function() { // when clicking to show details
				var details = $('<div class="lw_notices_details" id="lw_notices_details_'+s.id+'"/>'); // create a log
				$('.lw_notices_details').overlay('remove'); // remove any existing overlay log
				if(s.id&&s.log) { // if this notice is logged under an ID
					var notices = self.data('lw_notice_'+s.id); // get existing notices from this group
					$.each(notices,function() { // with each notice
						this.clone(true).appendTo(details); // add it to the log
					});
				} else { // otherwise, if we're just showing details
					notice.clone(true).removeAttr('id').appendTo(details); // add a clone of this notice only to the log					
				}
				details.overlay(); // and show the log as an overlay
				return false;
			});
		if(s.duration) { // if the message should be hidden automatically
			var timeout;
			notice.mouseover(function() { // onmouseover (mouseover, not mouseenter)
				clearTimeout(timeout); // clear the timeout
			}).mouseleave(function() { // on mouseleave
				timeout = setTimeout(function() { close.click(); },s.duration); // set the notice to hide
			}).mouseleave(); // and set it to hide now
		}
		var last = self.find('#'+s.id); // find any existing notice
		s.callback.apply(notice,[last.length]); // apply the callback with the notice as context
		if(s.id) { // if we're combining errors
			if(s.log) {
				var clone = notice.clone(true).removeAttr('id'), // clone this notice, with any events intact
					notices = self.data('lw_notice_'+s.id) || []; // get existing notices from this group
				notices.push(clone); // add the clone to the group
				self.data('lw_notice_'+s.id,notices); // and store it
				if(notices.length>1) { // if there are other notices like this
					showdetails.html((notices.length-1)+' more like this...').show(); // add the link to view more info and make sure it's showing
					var details = $('#lw_notices_details_'+s.id);
					if(details.length) { // and if the user is already viewing the log
						clone.clone(true).appendTo(details); // add the current notice to it
					}
				}
			}
			if(last.length) { // if there's an existing notice in this group
				last.replaceWith(notice.show()); // replace the existing one with the new one	
				return self; // and return now instead of sliding down
			}
		}
		var container = self.find('>.lw_notices');
		if(!container.length) var container = $('<div class="lw_notices lw_element"/>').appendTo(self); // create the container if it doesn't exist
		notice.hide().appendTo(container).slideDown(s.slideIn); // and finally show the notice	
		return self;
	},
	slideshow:function() { // turns an unordered list into a slideshow
		this.each(function() {
			var self=$(this),
				current,
				total = self.children().length,
				controls = $('<li class="lw_slideshow_controls'+(total>1 ? '' : ' lw_slideshow_single')+'"><div class="lw_slideshow_count"><span class="lw_slideshow_count_current">1</span> of <span class="lw_slideshow_count_total">'+total+'</span></div><a href="#" class="lw_slideshow_prev">&laquo; Previous</a><a href="#" class="lw_slideshow_next">Next &raquo;</a></li>'), // the controls
				count = controls.find('.lw_slideshow_count_current'),
				showslide = function(slide) { // when showing a slide
					count.html(slide.index()+1); // update the current index
					current = slide; // set the slide pointer
					self.stop() // stop any animation on the slideshow
						.children('.lw_slideshow_slide').stop().css('z-index',0); // and its children straightaway
					var height = self.height(), // current height
						targetHeight = slide.height(), // the height of the slide
						width = self.width(), // current width
						targetWidth = slide.width(); // the width of the slide
					slide.css({marginTop:-(targetHeight+parseInt(self.css('padding-top'))+parseInt(self.css('padding-bottom')))/2, marginLeft:-targetWidth/2,zIndex:'100'}); // center the slide vertically and horizontally and bump it to the top
					if(height>targetHeight||width>targetWidth) { // if we need to shrink the slideshow
						self.animate({'height':targetHeight,'width':targetWidth},750); // do it
						slide.delay(750); // and delay the slide for 750 ms
					}
					slide.fadeTo(750,1,function() { // now, fade in the slide
						slide.siblings('.lw_slideshow_slide').css('opacity',0); // after which we can safely hide the siblings 
						controls.find('.lw_slideshow_prev').toggleClass('lw_disabled',!current.prev('.lw_slideshow_slide').length); // and toggle the previous control state
						controls.find('.lw_slideshow_next').toggleClass('lw_disabled',!current.next('.lw_slideshow_slide').length); // and toggle the next control state
					}); 
					if(height<targetHeight||width<targetWidth) { // if we need to grow the slideshow
						self.delay(750).animate({'height':targetHeight,'width':targetWidth},750); // do it after 750 ms
					}
				};
			self.addClass('lw_slideshow') // initialize as a slideshow
				.height(self.children().eq(0).height()).width(self.width()) // which means fixing its height and width
				.children().addClass('lw_slideshow_slide').css({opacity:0,position:'absolute',top:'50%',left:'50%'}) // and hiding and positioning its children			
					.eq(0).whenloaded(function() { // when the first child has loaded
						showslide($(this)); // show the slide
					});
			controls.appendTo(self)
				.find('a').click(function() { // and attach the click handler to the controls
					var control = $(this);
					if(control.is('.lw_disabled')) return false; // do nothing if the control is disabled
					if(control.is('.lw_slideshow_next')) { // if it's the "next" link
						showslide(current.next());
					} else { // otherwise, it's the previous link
						showslide(current.prev());
					}
					return false; // cancel the click
				});
		});
		return this;// return the original element for chaining
	},
	whenloaded:function(callback) { // when all images have loaded
		var loadable = 'img,object,audio,video,iframe' // selector for elements with the load event
		this.each(function() {
			var self=this,
				elements = $(this).find(loadable), // get the list of loadable elements
				loaded = 0; // and the number that have loaded so far
			if($(this).is(loadable)) elements = elements.add(this); // add the current element if it's loadable
			elements.each(function() { // get each element
				if(this.complete) loaded++; // if it's already loaded, increment the counter
				else {
					$(this).load(function() { // otherwise, attach a load event
						if(++loaded==elements.length) callback.apply(self); // that increments the counter and checks if its time to run the callback
					});
				}
			});
			if(elements.length==loaded) callback.apply(self); // if the elements are all loaded, apply the callback immediately	
		});
		return this; // return the original element for chaining
	},
	placeholder:function(options) { // bootstrap HTML5 placeholder attributes for browsers that don’t support it
		options = options || {};
		var s = {
			style:options.style || 'lw_placeholder', // the class to add when the element is operating as a placeholder
			clear:options.clear || false // the elements which, when clicked, should wipe the placeholder
		};
		return this.each(function() { // with each matched element
			var self = $(this);
			if (this.placeholder && 'placeholder' in document.createElement(this.tagName)) return; // if the browser supports placeholders for this element, abort
			if(self.data('placeholder')) { // if a placeholder has already been set on this element
				return; // abort to avoid double-binding
			}
			if(!$.fn.placeholder_val) { // if we haven't already overidden .val()
				$.fn.placeholder_val = $.fn.val; // store the old version
				$.fn.val = function() { // and redefine the new version so that
					var placeholder = $(this).attr('placeholder');
					if(this.length==1&&placeholder) { // if this is one item with a placeholder attribute 
						if(!arguments.length&&placeholder==$(this).placeholder_val()) { // and it's a getter and the value is the placeholder
							return ''; // the value's an empty string
						} else if(arguments.length&&arguments[0]&&arguments[0]!=placeholder) { // but if we're setting an non-blank value and that value is not the placeholder
							$(this).removeClass(s.style); // remove the placeholder class
						}
					}
					return $.fn.placeholder_val.apply($(this),arguments); // in any other case, just call the original .val()
				}
			}
			self.data('placeholder',true); // flag this element as having a placeholder, so we'll never double-bind
			var placeholder = self.attr('placeholder'),
				clear = function() { // to clear the placeholder
					if(!self.val()) { // if the there's no real val
						self.removeClass(s.style).val(''); // blank the text and remove the placeholder style
					}
				};
			self.focus(clear)
				.blur(function() { // on blur
					var val = self.val();
					if(!val||val==placeholder) { // if there’s no text, or the text is the placeholder
						self.addClass(s.style).val(placeholder); // set the text to the placeholder and add the style
					} else {
						self.removeClass(s.style); // otherwise, kill the placeholder style
					}
				}).blur(); // and do it now
			self.parents('form').submit(clear);
			$(s.clear).click(clear);
		});
	},
	paginate:function() { // paginate; apply to the widget itself
		this.each(function() {
			var self = $(this),
				paginate = self.find('.lw_paginate');
			paginate.find('a').click(function() {
				if(livewhale.callbacks.beforePaginate.apply(this)) { // only continue if the before callback returns true
					paginate.addClass('lw_paginate_loading').append('<div class="lw_spinner"/>'); // add the spinner
					var placeholder = $('<li class="lw_paginate_placeholder"><div class="lw_spinner"/></li>').appendTo(self.find('>ul')); // add a spinner placeholder to the UL
					$.ajax({
						url:$(this).attr('href'), // URL to request
						success:function(response) { // on success
							self.replaceWith($(response).paginate()); // replace this with the new, longer list (and re-active the pagination)
							livewhale.callbacks.onPaginate.apply(this); // and call the completion callback
						},
						error:function() { // on failure
							if(livewhale.ajaxError) { // if logged in
								livewhale.ajaxError.apply(this,arguments); // call the generic LiveWhale ajaxError handler
							}
							placeholder.remove(); // and remove the placeholder
						}
					});			
				}
				return false;
			});
		});
		return this; // return original element for chaining		
	}
});

livewhale.previewImage = function(src) { // previews a LiveWhale image in an overlay
	var overlay = $('<div class="lw_image_preview_image"><div class="lw_spinner"/></div>').overlay({style:'lw_image_preview'});
	setTimeout(function() {
		$('<img src="'+src+'" alt="Image preview"/>').css('opacity',0).appendTo(overlay).whenloaded(function() {
			var width = $(this).width(),
				height = $(this).height();
			overlay.overlay('sizeTo',width-2,height-2,function() { // size the overlay to the image height
				overlay.find('.lw_spinner').remove();
				overlay.css({opacity:0,backgroundImage:'url('+src+')',backgroundPosition:'center center',backgroundRepeat:'no-repeat'}).fadeTo(500,1);
			});
		});
	},400);
}

livewhale.getCookie = function(name) {
	var cookieValue = '';
    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" : "");
}

livewhale.getVar = function(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(unescape(return_value));        
}

livewhale.stringToJSON = function(value) { // TODO: Search for uses of this
	var m = {'\b':'\\b', '\t':'\\t', '\n':'\\n', '\f':'\\f', '\r':'\\r', '"':'\\"', '\\':'\\\\'}, r = /["\\\x00-\x1f\x7f-\x9f]/g;
	return r.test(value) ? '"' + value.replace(r, function (a) {
		var c = m[a];
		if (c) return c;
		c = a.charCodeAt();
		return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
	}) + '"' : '"' + value + '"';
};

})(livewhale.jQuery);
