/* LiveWhale Common Frontend and Backend Effects */
/* (requires jQuery 1.4.2, jQuery UI) */
/* 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) livewhale = {}; // if the livewhale namespace doesn't exist, create it

livewhale.jQuery = jQuery;

(function($){

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
});

// Global Ajax
$.ajaxSetup({ // set default settings for Ajax requests
	timeout:livewhale.ajax_timeout*1000, // set timeout before reporting an error
	error:function(XMLHttpRequest, textStatus, errorThrown) { // on error
		return livewhale.ajaxError.apply(this,[XMLHttpRequest, textStatus, errorThrown]); // call the generic LiveWhale ajaxError handler
	}
});

livewhale.ajaxError = function(XMLHttpRequest, textStatus, errorThrown) {
	var details;
	if(!livewhale.getCookie('lw_pages_editor')) return false; // skip logging for non-logged-in users
	switch(textStatus) {
		case 'timeout': // request timed out
			details = 'Your request has taken too long to complete and timed out.';
			break;
		case 'parsererror': // wrong datatype (e.g. xml, JSON...)
			details = 'The returned content does not match the expected format: <em>'+XMLHttpRequest.responseText.replace(/</g,'&lt;').substr(0,100)+'</em>...';
			break;
		case 'error': // HTTP error
			details = 'The server returned the status error <em>'+XMLHttpRequest.status+'</em>.'; // otherwise, report the status error
			break;
		default: // misc
			details = 'There’s been an unknown error loading the content requested.'; // if the error is an unknown type
			break;
	}
	$('.lw_spinner').remove(); // remove any currently active spinners
	$.ajax({url:livewhale.livewhale_dir+'/?livewhale=log_error&error='+escape(details)+'&url='+escape(this.url),error:false,timeout:60000}); // log error
	$('body').notify({ // notify the user of the error
		id:'ajax',
		message:'Whoops, there was an error communicating with the server! Please check your internet connection and try again.',
		details:'<a href="'+this.url+'">'+this.url+'</a> : '+details,
		type:'failure'
	});		
	return false;
}

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({
	notify: function(options,type,callback) { // (message, 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;"')+'>×</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,
				controls = $('<li class="lw_slideshow_controls"><div class="lw_slideshow_count"><span class="lw_slideshow_count_current">1</span> of <span class="lw_slideshow_count_total">'+self.children().length+'</span></div><a href="#" class="lw_slideshow_prev">« Previous</a><a href="#" class="lw_slideshow_next">Next »</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
	},
	outerHTML: function() { // get an element's HTML, including the element itself
		alert('outerHTML'); // temporarily alert so we track when/if this is actually used!
		return $('<div/>').append(this.clone()).html();
	},
	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
							livewhale.ajaxError.apply(this,[XMLHttpRequest, textStatus, errorThrown]); // call the generic LiveWhale ajaxError handler
							placeholder.remove(); // and remove the placeholder
						}
					});			
				}
				return false;
			});
		});
		return this; // return original element for chaining		
	}
});

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) {
	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);