/* $Id $
 * This compiles the CDC JS code into one place to reduce the file count and try to put all their stuff together in one file,
 * this file is loaded on every page
 */
/*jslint forin: true */
/*global jQuery, Math, document, alert, confirm, $, window, unescape, setTimeout, scroll*/
var E360 = E360 || {};
(function($) {
	// SPECIAL automatic init method stuff which we probably should get rid of
	E360.initializer = function() {
		for (var prop in this) {
			if (prop !== 'initializeE360' && typeof(this[prop].initializeE360) === 'function') {
				this[prop].initializeE360();
			}
		}
	};
	$(document).ready( function() {
		E360.initializer();
	});

	// Utility methods
    /**
     * Check if a string contains a substring, case sensitive
     * @param {String} str
     * @param {String} substring
     */
    E360.strContains = function(str, substring) {
        return str.length >= substring.length && str.indexOf(substring) > -1;
    };
    
    /**
     * Check if a string starts with a substring, case sensitive
     * @param {String} str
     * @param {String} substring
     */
    E360.strStartsWith = function(str, substring) {
        return str.length >= substring.length && str.indexOf(substring) === 0;
    };

    /**
     * Check if a string ends with a substring, case sensitive
     * @param {String} str
     * @param {String} substring
     */
    E360.strEndsWith = function(str, substring) {
        return str.length >= substring.length && str.substr(str.length - substring.length) === substring;
    };

	function S4() {
	   return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
	}
	/**
	 * generate a UUID in a very fast and clean way
	 * e.g. 3F2504E0-4F89-11D3-9A0C-0305E82C3301
	 * @return a uuid
	 */
	E360.uuid = function() {
		// http://note19.com/2007/05/27/javascript-guid-generator/
		return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
	};

	/**
	 * Animate and show the loading spinner when loading a paginated page.
	 */
	E360.jpaginatorLoading = function(id) {
			$(id).slideUp('medium', function() { 
				$(this).html('<span class="loader-large"><em>Loading...</em></span>')
				.show();
			});
			return true;
	};

	/**
	 * Ensure all external links open in new window
	 *
	 * @param {string} selector to find the a tags
	 */
	E360.changeExternalLinkTargets = function(selector) {
		var links = $(selector);
		links.each(function() {
			if((this.href.indexOf('://') !== -1) && (this.href.indexOf('://' + document.domain) === -1)) {
				$(this).attr('target', '_blank');
			}
		});
	};

	/**
     * This will make form controls found by the selector select (highlight) the internal content
     * as long as it has not changed
     *
     * @param {string} itemsSelector a selector to choose the items
     */
    E360.initSelectUnchanged = function(itemsSelector){
        $(itemsSelector).focus(function(){
            // only select if the text has not changed
            if (this.value === this.defaultValue) {
                this.select();
            }
        });
    };
	/**
	 * Trigger show a block when click triggerd, can also hide another block
	 * 
	 * @param {string} clickTriggerSelector the selector for the items which are triggers
	 * @param {string} itemsToShowSelector the selector for the items to reveal
	 * @param {string} itemsToHideSelector [optional] selector for the items to hide, if set then hide the selected items
	 */
    E360.initClickToShow = function(clickTriggerSelector, itemsToShowSelector, itemsToHideSelector){
        $(clickTriggerSelector).click(function(){
			if (undefined !== itemsToHideSelector) {
				$(itemsToHideSelector).hide();
			}
			$(itemsToShowSelector).show(200);
            return false; // cancel the action
        });
    };
    /**
     * This will clear any form fields found by the selector if they have not changed and
     * will then submit the form found by the form selector
     *
     * @param {string} submitSelector selector for the submit button
     * @param {string} formSelector the selector for a form
     * @param {string} itemsSelector selects items within the form only
	 * @param {string} defaultValueField What field holds the default value. Either 'defaultValue' or 'title'.
     */
    E360.initClearUnchangedAndSubmit = function(submitSelector, formSelector, itemsSelector, defaultValueField){
		var form = $(formSelector);
		if (!defaultValueField) {
			defaultValueField = 'defaultValue';
		}
        $(submitSelector).click(function(){
            form.find(itemsSelector).each(function(i){
                // clear out the value if they are defaults before sending
                if (this.value === this[defaultValueField]) {
                    this.value = "";
                }
            });
            form.submit();
            return false; // cancel the action
        });
    };
    E360.initClickTagAdd = function(formSelector, formInputSelector, tagsSelector){
		var form = $(formSelector);
		var formInput = form.find(formInputSelector);
		$(tagsSelector).click(function(){
			var tagName = $(this).text();
			tagName = jQuery.trim(tagName);
			formInput.val(tagName);
			form.submit();
			return false; // cancel the action
		});
	};
	/**
	 * Initializes a multi-form handler which will create new things by
	 * appending copies of the repeatTemplate inside the container when
	 * the add item is clicked and removing the associated one when the delete item is clicked
	 * 
	 * @param {string}  repeatTemplate a template (or selector for the template content) which will be copied into the parent,
	 *                  all '{{i}}' values will be replaced by the current value of the counter
	 * @param {string}  containerSelector the selector for a container where the new things should be appended
	 * @param {string}  addSelector the selector for an item which causes the template to be replicated
	 * @param {string}  deleteSelector the selector for an item which causes a replicated template to be removed (should be inside the temple)
	 * @param {number}  initialCounter [optional] defaults to 1, set the starting position for the counter
	 */
	E360.initMultiForm = function(repeatTemplate, containerSelector, addSelector, deleteSelector, initialCounter) {
		if (undefined === initialCounter) {
			initialCounter = 1;
		}
		var template = $(repeatTemplate).html();
		var counter = initialCounter;
		var container = $(containerSelector);
		$(addSelector).click( function () {
			container.append(template.replace(/\{\{i\}\}/g, counter));
			var newElement = container.find(":last-child");
			newElement.find(deleteSelector).click( function () {
				var parentItem = $(this).parent();
				parentItem.hide();
				parentItem.remove();
				return false;
			});
			var inputs = newElement.find('input').defaultText();
			counter = counter + 1;
			return false;
	    });
	};
	/**
	 * Initialize school switcher if present
	 **/
	E360.initSchoolSwitcher = function() {
		var $schoolSwitcher = $('#school-switcher');
		if (!$schoolSwitcher.length) {
			return;
		}
		var $ps = $('p', $schoolSwitcher);
		$('#school-switcher-start').click(function(event) {
			event.preventDefault();
			$ps.toggle();
			$('#school-switcher-id').change(function() {
				var $form = $(this).parents('form');
				$form.submit();
			});
			$('#school-switcher-cancel').click(function() {
				event.preventDefault();
				$ps.toggle();
			});
		});
	};

    /**
     * This handles displaying notifications (messages) for the user in a standard way
     * 
     * Optionally the following dependencies if you want the pretty looking notifications:
        $jquery->uses('gritter');
        echo $html->css('gritter/jquery.gritter.css');
     * 
     * @param {Object} options should include at least:
     * status: "success" | "error",
     * message: a message to display to the user
     * and optionally can include:
     * title: any title to show the user (defaults to "Alert")
     * modal: true | false (defaults to false)
     * image: an image to include with the notification (defaults to none)
     */
    E360.notify = function(options) {
        if (options) {
            // only trigger if the options are set
            // STRING input is allowed for simple messages
            if (typeof options === 'string') {
                // assume it is a message
                options = {status: 'success', message: options};
            }
            if (typeof options === 'object' && options.message) {
                // INIT cleanup of options
                if (! options.status) {
                    options.status = "success"; // default status
                }
                if (! options.title) {
                    options.title = 'Alert'; // default title
                }
                if (! options.modal) {
                    options.modal = false; // default modal setting (non-modal)
                }
                if (! options.image) {
                    options.image = ''; // default to no image
                }

                var handled = false;

                // GRITTER - http://boedesign.com/blog/2009/07/11/growl-for-jquery-gritter/
/*                if (!handled && !options.modal && $.gritter) {
                    // attempt to use gritter for non-modal if available
                    var unique_id = $.gritter.add({
                        title: options.title, // (string | mandatory) the heading of the notification
                        text: options.message, // (string | mandatory) the text inside the notification
                        image: options.image,
                        sticky: options.sticky || false, // if true this requires a click to get rid of it
                        class_name: 'e360-notification', // (string | optional) the class name you want to apply to that specific message
                        fade_in_speed: 100, // how fast notifications fade in (string or int)
                        fade_out_speed: 100, // how fast the notices fade out
                        time: 4000 // hang on the screen for...
                    });
                    if (unique_id) {
                        handled = true;
                    }
                }
*/
                // DEFAULT cheesy notification using alert
                if (!handled) {
                    handled = true;
                    alert(options.message);
//                    alert(options.title + ": \n" + options.message);
                }
            } else {
                // should we do anything in the case that the input is invalid? (no message or not an object)
                alert('Warning: There was an error.');
            }
        }
    };
    /**
     * This handles general modal confirmation dialogs for users,
     * will trigger execution of the ok or cancel callbacks depending on what the user chooses
     * 
     * @param {Object} options should include at least:
     * message: a message to display to the user
     * okCallback: this is run when the user clicks the confirm button or enter, the options are passed
     * and optionally include these:
     * cancelCallback: this is run when the user clicks cancel or escape, the options are passed (default: close the confirm dialog)
     * okMsg: the confirm text (default: OK),
     * cancelMsg: the decline text (default: Cancel),
     * modal: true | false (default: true)
     */
    E360.confirm = function(options) {
        if (options) {
            // only trigger if the options are set
            if (typeof options === 'object' && options.message && options.okCallback) {
                // INIT - cleanup
                if (! options.okMsg) {
                    options.okMsg = "OK"; // default ok
                }
                if (! options.cancelMsg) {
                    options.cancelMsg = "Cancel"; // default ok
                }
                if (!options.cancelCallback || typeof options.cancelCallback !== 'function') {
                    options.cancelCallback = null;
                }
                if (typeof options.modal !== 'boolean') {
                    options.modal = true;
                }

                var handled = false;

                // DEFAULT JS confirm handler
                if (!handled) {
                    handled = true;
                    if ( confirm(options.message) ) {
                        options.okCallback(options);
                    } else {
                        if (options.cancelCallback) {
                            options.cancelCallback(options);
                        }
                    }
                }
            } else {
                // should we do anything in the case that the input is invalid? (no message or not an object)
                alert('Warning: There was an error.');
            }
        }
    };
    /**
     * Initialize enable/disable behavior of new global-style buttons
     *   - you can change enabled setting with jQuery().trigger('enable' or 'disable')
     *   - disabled buttons automatically stop click events or other handlers
     */
    E360.initButtonLinks = function() {
		jQuery('a.btn_global, a.btn_global-disabled')
			.click(function(event) {
				if (jQuery(this).hasClass('btn_global-disabled')) {
					event.stopImmediatePropagation();
					event.preventDefault();
				}
			})
			.bind('enable', function(event) {
				jQuery(this).addClass('btn_global').removeClass('btn_global-disabled');
			})
			.bind('disable', function(event) {
				jQuery(this).addClass('btn_global-disabled').removeClass('btn_global');
			});
    };



	/**
	 * Reload a paginated page created with jpaginator helper.
	 *
	 * You must create your pagination using jpaginator and use the jpaging element.
	 *
	 * @param string selector Selector or DOM object in which the jpaging element is contained.
	 * @param string selector Selector or DOM object in which to load the page contents.
	 */
	E360.reloadPaginatedPage = function(selector, update) {
		var url = $(selector).find('.e3-paging .current-page').attr('rel');
		$(update).load(url);
	};


	/**
	 * Handle the 'Add to Favourite' and 'Remove from Favourites' links.
	 */
	E360.Favorites = {

		// track if we are already processing to prevent double clicks
		processing: false,

		initializeE360: function() {
			$('a.favorite_link').bind('click', this.handleClick);
		},

		handleClick: function(event) {
			var self = this;
			var link = event.target;

			event.preventDefault();
			event.stopPropagation();
			if (self.processing) {
				return false;
			}
			self.processing = true;
			$(link).addClass('favorite_link_loading');

			var action = link.rel;
			var idSplit = link.id.split('_');
			var model = idSplit[2];
			var modelId = idSplit[3];

			$.ajax({
				type: 'POST',
				url: '/favorites/manage/'+action+'/'+model+'/'+modelId,

				// handle AJAX error, probably server communication issue
				error: function(request, status) {
					E360.notify({
						message: 'Problem updating favourites: '+status,
						title: 'Error',
						status: 'error'
					});
				},


				// handle successfull AJAX reuest
				success: function(response, status, request) {
					var message;
					response = $.evalJSON(response);
					// handle errors encountered during process
					if (typeof response.status === 'undefined' || response.status !== 'success') {
						message = 'Error updating your favourites.';
						if (typeof response.message !== undefined) {
							message = response.message;
						}
						E360.notify({
							message: message,
							title: 'Error',
							status: 'error'
						});
						return;
					}


					// handle success situation
					// Switch the link to the opposite action
					// If you change the text here, change it in the En360Helper::favoriteLink()
					switch (action) {
						case 'add': 
							$(link)
								.html('Remove from Favourites')
								.attr('rel', 'remove');
							message = 'Added to your favourites.';
							break;
						case 'remove':
							$(link)
								.html('Add to Favourites')
								.attr('rel', 'add');
							message = 'Removed from your favourites.';
							break;
					}

					E360.notify({
						message: message,
						title: 'Favourites'
					});
					$(link)
						.removeClass('favorite_link_'+action)
						.addClass('favorite_link_'+link.rel);
					return false;
				},

				// remove the spinner
				complete: function(response, status) {
					$(link).removeClass('favorite_link_loading');
					self.processing = false;
				}
			}); // end ajax() call 

			return false;
		}
	};


	/**
	 * Show a spinner overlay on DOM elements.
	 */
	$.fn.showLoader = function() {
		$('<div class="loader-overlay"></div>')
				// loader-large jqmOverlay" 
			.appendTo(this)
			.css({
				'z-index': this.css('z-index') + 1,
				width: this.outerWidth(),
				height: this.outerHeight()
			});
		return this;
	};

	/**
	 * Hide spinner overlay.
	 */
	$.fn.hideLoader = function() {
		$('.loader-overlay', this).remove();
		return this;
	};

	/**
	 * Extend jquery.ui.tabs for e360's default AJAX tab behavior. 
	 *
	 * Usage: Same as jquery.ui.tabs except:
	 *   - Include a #tab_panel in your HTML.
	 *   - Do not set the spinner option.
	 */
	$.fn.ajaxTabs = function(options) {
		var defaults = {
			// cache tabs by default
			cache: true,
			// we'll do our own spinner
            spinner:'',
			// the panel where we'll load tabs
			panelContainer: '#tab_panels'
		};
		options = $.extend(defaults, options);

		var loadingId = options.panelContainer + '_loading';
		loadingId = loadingId.replace('#', '');
		var $panelContainer = $(options.panelContainer);
		$panelContainer.before('<div id="'+loadingId+'" class="block tab_loader result"><span class="loader-large"><em>Loading...</em></span></div>');
		var $loading = $('#'+loadingId);

		var loadingStart = function(event, ui) {
			$panelContainer.find('.ui-tabs-panel').not('.ui-tabs-hide').addClass('ui-tabs-hide');
			$loading.show();
		};

		var loadingEnd = function(event, ui) {
			$loading.hide();
		};

		return this.tabs(options)
			.bind('tabsselect', loadingStart)
			.bind('tabsshow', loadingEnd)
			.bind('tabsload', loadingEnd);
	};
}(jQuery));


var Inflector = Inflector || {};
(function($) {
    Inflector.upperCaseWords = function(string) {
        var ucWords = '';
        
        for (var i=0, words = string.split(' '), limiti=words.length; i < limiti; i++) {
            ucWords = ucWords + words[i].substr(0, 1).toUpperCase() + words[i].substr(1);
        }
        
        return ucWords;
    };
    
    Inflector.classify = function(string) {
        return Inflector.upperCaseWords(string.replace(/_/g, ' '));
    };
}(jQuery));

// CDC OO like Class stuff below here -AZ **********

/**
 * Create a basic prototypal inheritance Class.
 * 
 * Create new classes with Class.create({prototype object});
 * Extend a class with Class.extend({prototype object});
 * Tape on new methods to all existing instances with Class.implement({object});
 * 
 * Classes can have an initializeClass() function which acts as a constructor for the prototype
 */
function Class (features) {
	var klass = function (noStart) {
		// run the initializeClass method when this "klass" is "constructed"
		if (typeof this.initializeClass === 'function' && noStart !== 'noInit') {
			// http://www.webreference.com/js/column26/apply.html
			return this.initializeClass.apply(this, arguments);
		}
		return this;
	};
	for (var key in this) {
		klass[key] = this[key];
	}
	klass.prototype = features;
	return klass;
}

Class.prototype.extend = function (features) {
	var oldProto, oldFunc, newFunc, func;
	oldProto = new this('noInit');

	var makeParent = function(parent, current) {
		return function () {
			this.parent = parent;
			return current.apply(this, arguments);
		};
	};

	for (var key in features) {
		oldFunc = oldProto[key];
		newFunc = features[key];
		if (typeof oldFunc != 'function' || typeof newFunc != 'function') {
			func = newFunc;
		} else {
			func = makeParent(oldFunc, newFunc);
		}
		oldProto[key] = func;
	}
	return new Class(oldProto);
};

Class.prototype.implement = function (features) {
	for (var key in features) {
		this.prototype[key] = features[key];
	}
};

/**
 * Empty function good for comparing to empty functions
 */
Class.empty = function () { };

/**
 * Add bind function to all functions.
 *
 * lets change 'this' in any function.
 */
if (typeof Function.prototype.bind !== 'function') {
	Function.prototype.bind = function (obj) {
		var method = this;
		return function() {
			return method.apply(obj, arguments);
		};
	};
}

// 
// Template Class for parsing and building of Templates that can be evaluated with values
//
// @param string templateString  The template to be used later on.
// @param string  pattern  Regular Expression to change the template tags of the Template
//
var Template = function (templateString, pattern) {
	this._template = null;
	this._data = {};

	// Default Syntax matches {{ tagName }}
	this.pattern = /\\?\{\{([^}]+)\}\}/mg;
	this._template = unescape(templateString);
	this.pattern = pattern || this.pattern;
	return this;
};

Template.prototype = {
	/**
	 * Evaluate a Template and replace the template tags with the data in the 
	 * Data object.
	 * 
	 * @param Object dataset Data that will fill the template out.
	 * @return String Template with datavalues replaced.
	 */
	evaluate : function (dataset) {
		return this._template.replace(this.pattern, function(match, name){
			if (match.charAt(0) === '\\') {
				return match.slice(1);
			}
			var dataPath = dataset,
			    path = name.split('.');
			for (var i in path) {
				dataPath = dataPath[path[i]];
				if(dataPath === undefined) {
					return '';
				}
				if(typeof(dataPath) !== 'object') {
					return dataPath;
				}
			}
			return '';
		});
	}
};

//
// multi form Class
// 
// Create forms widget sets with a template allowing many records to be
// created in one form.
// 
// 
// @param string template DOM ID of template Block.
// @param {Object} options
// 
// Options object takes a number of options
//  container : the dom id of the container element for the added form elements.
//  addButton : the dom id of the create new form button.
//  deleteButton : set to true to have events attached to the delete button
//  deleteButtonClass : the class name of the delete button.
//  count : what to start the internal instance counter at. (default 0)
//  clearContent :  If true, will clear the original content when first form is added
//                  also when last form is removed will replace original content.
//  lineElementClass : the class name of each multi form element defaults to '.line-block'
//  onComplete : Call backs to run after Template block is added in. Scoped to filled TemplateBlock
//  onFirst : Callback to run on first template block creation. Scoped to filled TemplateBlock
//  max : maximum number of rows allowed
var MultiForm = function (template, options) {
	this.count = 0;
	this.originalContent = null;
	this.container =  null;
	
	this.options = {
		deleteButtonClass : '.button.delete',
		deleteButton : true,
		lineElement : '.line-block',
		container : '',
		addButton : '',
		count : 0,
		clearContent: true,
		onComplete : function (){return true;},
		onFirst : function (){return true;},
		min : 0,
		max : 1000,
		maxErrorMessage : 'You have reached the limit of 1000 items.',
		minErrorMessage : 'You must have at least 0 items.'
	};
	this.options = jQuery.extend({}, this.options, options);
	
	this.template = new Template(template);
	this.container = jQuery(options.container);
	// count is always the current count of MultiForm elements on the page
	this.count = this.options.count || this.container.find(this.options.lineElement).length;
	// i is not decremented when deleting, to prevent subsequent input names from overlapping
	this.i = this.count;
	var addButton = jQuery(options.addButton);
	addButton.bind('click', {self : this}, this.buttonEvent);
	this.attachDeleteEvent();
	return this;
};

// Public prototype methods.
MultiForm.prototype = {
	// Button event for 'add button's
	buttonEvent : function (event) {
		var self = event.data.self;
		var templateData = {
			i : self.i,
			uuid: E360.uuid()
		};
		var filledTemplate = self.template.evaluate(templateData);
		if (self.count >= self.options.max) {
			E360.nofity({status: 'error', message: self.options.maxErrorMessage});
			return false;
		}
		if (self.count === 0 && self.options.clearContent) {
			self.originalContent = self.container.html();
			self.container.empty();
		}
		var el = jQuery(filledTemplate);
		self.container.append(el);
		self.count++;
		self.i++;
		if (self.count === 1) {
			self.options.onFirst.call(el);
		}
		self.attachDeleteEvent();
		self.options.onComplete.call(el);
		return false;
	},

	// Run After block has been added, attach events to delete button
	attachDeleteEvent : function () {
		var self = this;
		jQuery(this.container).find(this.options.deleteButtonClass).each(function (button) {
            button = jQuery(this); 
			button.unbind('click');
			var parentEl = button.parents(self.options.lineElement);
			button.bind('click', function(event) {
				event.preventDefault();
				if ( (self.count-1) < self.options.min) {
					E360.notify({status: 'error', message: self.options.minErrorMessage});
					return false;
				}
				parentEl.remove();
				self.count--;
				if (self.count === 0 && self.options.clearContent) {
					self.container.html(self.originalContent);
				}
				return true;
			});
		});
	}
};

//
// Tag Picker Class, handles event binding for Tag Picker element
// @see elements/layout/tag_picker.ctp
//
// @param options Options to use for the object
var TagPicker = function (options) {
	this.options = jQuery.extend({}, this.defaults, options);
	
	var buttons = jQuery.makeArray(this.options.button);
	for (var i=0; i < buttons.length; i++) {
		this.createButton(buttons[i]);
	}
	this.input = jQuery(this.options.input);
	this.addTags(this.options.tagList);
	this.tagList.hide();
};

//Public prototype methods.
TagPicker.prototype = {
	defaults : {
		button : ["a.btn_tags", "#predefined-tags"],
		input : '#TagPicker',
		tagList :  'div.tags-list',
		tagSelector : 'a'
	},
	createButton : function (button) {
		var self = this;

		if (button.fadeIn === undefined) {
			button = jQuery(button);
		}
		button.bind('click', function(event) {
			event.preventDefault();
			
			if (self.tagList.is(':visible')) {
				self.tagList.fadeOut();
			} else {
				self.highlightTags();
				self.tagList.fadeIn();
			}
		});
	},
	addTags : function (tagContainer) {
		var $tagContainer = jQuery(tagContainer);
		this.tagList = $tagContainer;
		var links = jQuery(this.options.tagSelector, $tagContainer);
		var self = this;

		function _addTagToList (event) {
			event.preventDefault();
			var tagText = jQuery(this).text();
			var currentTags = jQuery(self.input).val();
			if (currentTags.length === 0) {
				self.input.val(tagText);
			} else {
				self.input.val(currentTags + ", " + tagText);
			}
			var er = new RegExp('^' + tagText.replace(/\(/,'\\(').replace(/\)/,'\\)') + '$', 'i');
			jQuery(self.options.tagSelector, self.tagList).each(function() {
				if (jQuery(this).text().match(er)) {
					jQuery(this).replaceWith('<b>' + tagText + '</b>');
				}
			});
		}

		links.bind('click', _addTagToList);
	},
	highlightTags : function () {
		var self = this;
		var currentTags;
		var val = jQuery(self.input).val();
		if (val === undefined) {
			currentTags = [];
		} else {
			currentTags = val.split(',');
		}
		var links = jQuery(this.options.tagSelector, this.tagList);
		links.each(function() {
			var tag = jQuery(this);
			var tagText = tag.text();
			jQuery(currentTags).each(function() {
				var currentTag = this.replace(/^\s+|\s+$/g, '');
				var er = new RegExp('^' + currentTag + '$', 'i');
				if (tagText.match(er)) {
					tag.replaceWith('<b>' + tagText + '</b>');
				}
			});
		});
	}
};

//
// Feedback manager
// Handles fetching all feedback sets, and attaching events for forms if they are visible.
var FeedbackManager = function () {
	var self,
		options,
		buttons,
		defaults = {
			submitUrl : window.basePath + 'items/feedback/',
			getFeedback : '.lm-item-feedback',
			submitButton : '.btn_submit'
		};
	
	return {
		initializeClass : function (opts) {
			self = this;
			options = jQuery.extend({}, defaults, opts);
			buttons = jQuery(options.getFeedback);

			buttons.toggle(self.getFeedback, self.hideFeedback);
			if (window.location.href.match(/feedback\:all/)) {
				self.queueFeedbackRequests();
			}
		},
		
		bindEvents : function (element) {
			element.next().find(options.getFeedback).removeClass('loading');
			element.find('input[type=button]').bind('click', function (event) {
				var inputs = jQuery(this)
					.parent().addClass('loading')
					.find('input, textarea');
				var data = {};
				var button = jQuery(this).parent().parent().next().find(options.getFeedback);

				for (var i = 0, len = inputs.length; i < len; i++) {
					data[inputs.eq(i).attr('name')] = inputs.eq(i).val();
				}
				
				if(data['data[Feedback][text]'].match(/^\s*$/)) {
					element.find('span.feedback-message').html('You haven\'t added any feedback yet');
					jQuery(this).parent().removeClass('loading');
					return false;
				} else {
					element.find('span.feedback-message').html('');
				}

				var $profile = jQuery("#FeedbackProfileId");
				var itemId = data["data[Feedback][item_id]"];
				var profileId = $profile.size() > 0 ? $profile.val() : false;

				jQuery.ajax({
					type : 'post',
					data : data,
					url : options.submitUrl + itemId + (profileId ? '/' + profileId : ''),
					success : function (response, status) {
						element.html(response);
						button.text(parseInt(button.text(), 10) + 1);
						self.bindEvents(element);
					}
				});
				return true;
			});
			element.find('a.feedback-delete-button').bind('click', function (event) {
				event.preventDefault();
				E360.confirm({
					message: 'Are you sure you want to delete your feedback?',
					okCallback: function(options) {
						jQuery.ajax({
							type: 'post',
							dataType: 'json',
							url: event.currentTarget.href + '.json',
							success: function(response, status) {

								// check for error
								if (!response.status || response.status !== "success") {
									E360.notify({
										status: 'error',
										message: 'Unable to remove feedback. ' + response.data.message
									});
									return;
								} 

								// success, remove feedback from display
								jQuery(event.currentTarget).closest('.feedback').fadeOut('slow');
								E360.notify({status:'success', message:'Your feedback was removed.'});
								return;
							}
						});
					}
				});
			});
		},

		getFeedback : function (event) {
			event.preventDefault();
            var id = this.id;
            id = id.replace(/^lm-item-feedback-button-/, '');
			var $this = jQuery(this);
            var $target = jQuery('#lm-feedback-'+id);
			$target.html('<div class="loading">loading...</div>');
            $target.load($this.attr('rel'), 
                function (responseText, status) {
                    self.bindEvents(jQuery(this));
                }
            );
		},

		hideFeedback : function (event) {
			event.preventDefault();
            var id = this.id;
            id = id.replace(/^lm-item-feedback-button-/, '');
            jQuery('#lm-feedback-'+id).empty();
		},
		
		queueFeedbackRequests: function () {
			jQuery(options.getFeedback).each(function () {
				jQuery(this).trigger('click');
			});
		}
	};
}();


//
// Global object to hold method to bind
// and create forms for the course contents when
// the user is a moderator.
// also handles event binding for folder show/hide
//
var CourseContents = {
	initializeClass: function () {
		this.initFolders();
		if (jQuery('#quick-create-page').length) {
			this.initForms();
		}
	},

	initFolders: function () {
		jQuery('.course-pages').hide();
		jQuery('.current .course-pages').show().addClass('stayopen');
		jQuery('#course-contents .folder').click(function(event) {
			if(!jQuery(this).parent().find('.course-pages').hasClass('stayopen')) {
				jQuery(this).parent()
					.toggleClass('current')
					.find('.course-pages').toggle();
			}
			return false;
		});
	},
	
	initForms: function () {
		this.attachPageAdd();
		this.attachFolderAdd();
		this.attachRenameFolder();
		this.attachRenamePage();

		jQuery(document).click(this.clearQuickForms);
	},
	
	clearQuickForms: function () {
		jQuery('.add-page, .add-folder, .rename-folder-form, .rename-page-form').fadeOut('fast', function () {
			jQuery(this).remove();
		});
	},
	
	attachPageAdd: function () {
		var PageAddTemplate = new Template(jQuery('#quick-create-page').html());
		jQuery('li.add-page-button').bind('click', function (event) {
			CourseContents.clearQuickForms();
			var $this = jQuery(this);
			event.preventDefault();
			event.stopPropagation();
			var folderId = $this.attr('id').split('-')[2];
			var pageForm = jQuery(PageAddTemplate.evaluate({folderId: folderId}));
			pageForm.submit(function (event) {
				var holder = jQuery(this).find('p.blank-slate-message');
				holder.hide();
				var value = jQuery.trim(jQuery(this).find('input[type=text]').val());
				if (value === '' || value === 'Untitled Page') {
					holder.show().html('Please choose a title that describes the page content');
					event.preventDefault();
					event.stopPropagation();
				}
			});

			$this.append(pageForm);
			pageForm.find('a.cancel').bind('click', function (event) {
				jQuery(document).trigger('click', event);
				return false;
			});
			pageForm.fadeIn('fast').bind('click', function (event) {
				event.stopPropagation();
			});
			pageForm.find('input[type=text]').trigger('focus');
		});
	},

	attachFolderAdd: function () {
		var folderAdd = jQuery('#quick-create-folder');
		var folderAddForm = jQuery(folderAdd.html());
		jQuery('li.add-folder-button').bind('click', function (event) {
			folderAddForm.submit(function(event) {
				var holder = jQuery(this).find('p.blank-slate-message');
				holder.hide();
				var value = jQuery.trim(jQuery(this).find('input[type=text]').val());
				if (value === '' || value === 'Untitled Folder') {
					holder.show().html('Please choose a name for the folder');
					event.preventDefault();
					event.stopPropagation();
				}
			});

			CourseContents.clearQuickForms();
			var $this = jQuery(this);
			event.preventDefault();
			event.stopPropagation();
			$this.append(folderAddForm);
			$this.find('a.cancel').bind('click', function (event) {
				jQuery(document).trigger('click', event);
				return false;
			});
			$this.find('form').fadeIn('fast').bind('click', function (event) {
				event.stopPropagation();
			});
			$this.find('input[type=text]').trigger('focus');
		});
	},

	attachRenamePage: function () {
		var FolderRenameTemplate = new Template(jQuery('#quick-page-rename').val());
		jQuery('a.rename-page-trigger').bind('click', function(event) {
			event.preventDefault();
			event.stopPropagation();

			jQuery('a#rename-page-' + E360.pageId).click();
		});
		jQuery('a.folder-page').filter(function() {
			return jQuery(this).html() === 'Untitled Page';
		}).click(function (event) {
			event.preventDefault();
			event.stopPropagation();

			jQuery(this).nextAll('a.rename-page').click();
		});
		jQuery('a.rename-page').bind('click', function (event) {
			CourseContents.clearQuickForms();
			event.preventDefault();
			event.stopPropagation();

			var $this = jQuery(this);
			var pageId = $this.attr('id').split('-')[2];
			var pageName = jQuery.trim($this.parent().find('a.folder-page').text());
			var pageForm = jQuery(FolderRenameTemplate.evaluate({pageId: pageId, name: pageName}));

			pageForm.submit(function (event) {
				var holder = jQuery(this).find('p.blank-slate-message');
				holder.hide();
				var value = jQuery.trim(jQuery(this).find('input[type=text]').val());
				if (value === '' || value === 'Untitled Page') {
					holder.show().html('Please choose a title that describes the page content');
					event.preventDefault();
					event.stopPropagation();
				}
			});

			$this.parent().append(pageForm);
			pageForm.find('input#PageName').val(pageName);
			if(pageName === 'Untitled Page' || pageName === '') {
				pageForm.find('p.blank-slate-message').css('display', 'block');
			}
			pageForm.fadeIn('fast')
				.bind('click', function (event) {
					event.stopPropagation();
				});
			pageForm.find('a.cancel').click(function (event) {
				jQuery(document).trigger('click', event);
				return false;
			});
			pageForm.find('input[type=text]').trigger('focus');
		});
	},

	attachRenameFolder: function () {
		var FolderRenameTemplate = new Template(jQuery('#quick-folder-rename').val());
		jQuery('a.rename-folder-trigger').bind('click', function(event) {
			event.preventDefault();
			event.stopPropagation();

			jQuery('a#rename-folder-' + E360.folderId).click();
		});
		jQuery('a.rename-folder').bind('click', function (event) {
			CourseContents.clearQuickForms();
			event.preventDefault();
			event.stopPropagation();

			var $this = jQuery(this);
			var folderId = $this.attr('id').split('-')[2];
			var folderName = $this.parent().find('a.folder').text();
			var folderForm = jQuery(FolderRenameTemplate.evaluate({folderId: folderId, name: folderName}));

			folderForm.submit(function(event) {
				var holder = jQuery(this).find('p.blank-slate-message');
				holder.hide();
				var value = jQuery.trim(jQuery(this).find('input[type=text]').val());
				if(value === '' || value === 'Untitled Folder') {
					holder.show().html('Please choose a name for the folder');
					event.preventDefault();
					event.stopPropagation();
				}
			});

			$this.parent().append(folderForm);
			folderForm.fadeIn('fast')
				.bind('click', function (event) {
					event.stopPropagation();
				});
			folderForm.find('a.cancel').bind('click', function (event) {
				jQuery(document).trigger('click', event);
				return false;
			});
			folderForm.find('input[type=text]').trigger('focus');
		});
	}
};

// Generic Form Behaviors

var GenericFormEvents = {
	selectRadioLi: function (event) {
		jQuery(this).parents('ul').find('li').removeClass('selected');
		jQuery(this).parents('li').addClass('selected');
	}
};

/**
 * Adds default text to a form input that is cleared on focus and submit.
 */
jQuery.fn.defaultText = function() {
	return this.each(function() {
		var self = this;
		var clearText = function() {
			// If there is text 
			if (jQuery(self).val() === jQuery(self).attr('title')) {
				jQuery(self).val('');
			}
			// return true so submit event continues
			return true;
		};
		var addText = function() {
			if (jQuery(self).val() === '') {
				jQuery(self).val(jQuery(self).attr('title'));
			}
		};
		jQuery(self)
			// clear default text on focus
			.bind('focus', clearText)
			// add default text back if lose focus and still empty
			.bind('blur', addText)
			// Clear default text on submit
			.closest('form').bind('submit', clearText);
		addText.call(self);
	});
};


// Tag flyout Javascript.
// Handles positioning the fly out div so that users can add Need Tags to their profile.


// Flyout class (why is this a separate class?)
var Flyout = new Class({
	enabled: true,
    defaults: {
        template: '#tag-flyout-template',
        targets: '.hover-tags li'
    },
	flyout: null,
    initializeClass: function (options) {
		if (!this.enabled) {
			return false;
		}

        this.options = jQuery.extend({}, this.defaults, options);
        var templateContainer = jQuery(this.options.template);
        if (templateContainer.length === 0) {
            return true;
        }
        this.flyoutTemplate = new Template(templateContainer.html());
        this.bindEvents();

		return true;
    },
    bindEvents: function () {
        var self = this;
		if (!this.options || !this.options.targets) {
			return;
		}
        jQuery(this.options.targets).live('hover', function (event) {
            return self.mouseEnter.apply(this, [event, self]);
        }, function (event) {
            return self.mouseLeave.apply(this, [event, self]);
        }).live('click', function(event) {
			var result = self.click.apply(this, [event, self]);
			if (result === false && event.returnValue) {
				event.returnValue = result;
			}
			return result;
		});
    },
    mouseEnter: function (event, self) {
    },
    mouseLeave: function (event, self) {
    },
    click: function (event, self) {
    }
});

// TagFlyoutPopup - Subclass for Tag Flyouts.
var TagFlyoutPopup = Flyout.extend({
    defaults: {
        template: '#tag-flyout-popup-template',
        targets: '.hover-tags li'
    },
	click: function(event, self) {
		event.preventDefault();

        var $tagElement = jQuery(this);
        var weight = $tagElement.attr("class");
        var tagNameValue = $tagElement.find("a").text();
        var keyNameValue = tagNameValue.toLowerCase().replace(/[^a-z0-9]/g, '');
        var tagId = $tagElement.find("a").attr("id").substr(4);
        var data = {
            'tagname': tagNameValue,
            'keyname': keyNameValue,
            'tagid': tagId
        };
        

		var $body = jQuery('body');
		
		var flyoutId = '#' + keyNameValue + '-flyout';
		self.flyout = $body.find(flyoutId);
		if (self.flyout.length === 0) {
			var flyoutHtml = jQuery(self.flyoutTemplate.evaluate(data));
			$body.append(flyoutHtml);
			self.flyout = $body.find(flyoutId);
		}
		self.flyout.tagElement = $tagElement;
		self.status();
		self.flyout
			.css('top', (jQuery(window).scrollTop() + 100).toString() + 'px')
			.jqm({
				modal: true,
				closeClass: 'close-dialog',
				toTop: true,
				overlay: 50
			})
			.jqmShow();

		var cookieData = jQuery.cookies.get('needTags');
		var needTags = {};
		if (cookieData) {
			needTags = jQuery.evalJSON(cookieData);
		}

		var deleteLink = jQuery("a.c-delete", this.flyout).hide();

		jQuery("input.radio", self.flyout).click(function() {
			var level = parseInt(jQuery(this).attr("class").replace(/^.*\bc(\d+)\b.*$/, '$1'), 10);
			if (isNaN(level)) {
				return false;
			}

			return self.setNeed(level);
		});

		deleteLink.click(function() {
			self.removeNeed();
			return false;
		});

		if (needTags[keyNameValue] && needTags[keyNameValue] !== '0') {
			self.setNeed(needTags[keyNameValue], false);
		}

		return false;
    },
	status: function(type) {
		var self = this;
		jQuery("div.status-messages .status-message", this.flyout).hide();
		if (type) {
			var status = jQuery("div.status-messages .status-message-" + type, this.flyout);
			jQuery("div.status-messages", this.flyout).show();
			status.show();
			if (type === "added" || type === "removed") {
				setTimeout(function() {
					status.fadeOut("fast", function() {
						jQuery("div.status-messages", self.flyout).hide();
					});
				}, 2000);
			}
		} else {
			jQuery("div.status-messages", this.flyout).hide();
		}
	},
	setNeed: function(level, post) {
		post = typeof(post) === 'undefined' || post;
		var input = jQuery("input.c" + level, this.flyout);
		var url = input.val();
		if (!url) {
			return false;
		}

		if (post) {
			this.status("loading");
		} else {
			input.attr("checked", true);
		}

		if (!post || this.postUrl(url)) {
			if (post) {

				/* update needs cloud on welcome/dashboard */
				var sameNeedTag = null;
				if(jQuery('#your-needs')) {
					if(this.flyout.tagElement && this.flyout.tagElement.parent('#your-needs').length) {
						this.flyout.tagElement.show().removeClass().addClass('c' + level);
					} else if((sameNeedTag = jQuery('#your-needs #' + this.flyout.tagElement.find('a').attr('id')).parent('li')) && sameNeedTag.length) {
						this.flyout.tagElement = sameNeedTag.show().removeClass().addClass('c' + level);
					} else {
						jQuery('#your-needs ul.hover-tags').append('<li class="c' + level + '">' + this.flyout.tagElement.html() + '</li>');
						this.flyout.tagElement = jQuery('#your-needs a#' + this.flyout.tagElement.find('a').attr('id')).parent('li');
					}
				}

				this.status("added");
			}
			jQuery("a.c-delete", this.flyout).show();
		} else {
			input.attr("checked", false);
			this.status("failed");
		}
		return true;
	},
	removeNeed: function() {
		var $delete = jQuery("a.c-delete", this.flyout);
		var url = $delete.attr("href");
		if (!url) {
			return false;
		}

		this.status("loading");
		if (this.postUrl(url)) {

			/* update needs cloud on welcome/dashboard */
			var sameNeedTag = null;
			if(jQuery('#your-needs')) {
				if(this.flyout.tagElement && this.flyout.tagElement.parent('#your-needs').length) {
					this.flyout.tagElement.hide();
				} else if((sameNeedTag = jQuery('#your-needs #' + this.flyout.tagElement.find('a').attr('id')).parent('li')) && sameNeedTag.length) {
					sameNeedTag.hide();
				}
			}

			this.status("removed");
			jQuery("input.radio", this.flyout).attr("checked", false);
			$delete.hide();
		} else {
			this.status("failed");
		}
		return true;
	},
	postUrl: function(url, data) {
		var success = false;
		jQuery.ajax({
			async: false,
			type: "POST",
			url: url,
			//dataType: "json",
			data: data || null,
			success: function(data, textStatus) {
				if (!textStatus || textStatus !== "success") {
					success = false;
				} else {
					success = true;
					if (data.data) {
						success = data.data;
					}
				}
			}
		});
		return success;
	}
});

// TagFlyout - Subclass for Tag Flyouts.
var TagFlyout = Flyout.extend({
	enabled: false,
    mouseEnter: function (event, self) {
        var $tagElement = jQuery(this);
        var weight = $tagElement.attr("class");
        var tagNameValue = $tagElement.find("a").text();
        var keyNameValue = tagNameValue.toLowerCase().replace(/[^a-z0-9]/g, '');
        var tagId = $tagElement.find("a").attr("id").substr(4);
        var data = {
            'tagname': tagNameValue,
            'keyname': keyNameValue,
            'tagid': tagId
        };
        
        var flyoutHtml = jQuery(self.flyoutTemplate.evaluate(data));
        //var flyoutPosition = self.calcPosition($tagElement, flyoutHtml);
        var flyoutPosition = self.calcMousePosition(event, flyoutHtml);
        flyoutHtml.css('top', flyoutPosition.top);
        flyoutHtml.css('left', flyoutPosition.left);
//        flyoutHtml.css('top', 0);
//        flyoutHtml.css('left', 0);
        //$tagElement.append(flyoutHtml); // this does not position correctly - 665
		var $body = jQuery('body');
		$body.append(flyoutHtml);
		var $flyout = $body.find('#' + keyNameValue + '-flyout');
        
        //$flyout.fadeIn("normal");
		// delay the popup appearing, the popup is removed on mouse out so no need to handle the case of running over multiple items
		setTimeout( function() {
			$flyout.fadeIn("normal");
			$flyout.mouseenter(function(){
				// sets the attribute to keep this alive when we leave the tag li
				jQuery(this).addClass('keepflyoutalive');
			});
			$flyout.mouseleave(function(){
				jQuery(this).remove();
			});
		}, 300);

		var cookieData = $.cookies.get('needTags');
		var needTags = {};
		if (cookieData) {
			needTags = $.evalJSON(cookieData);
		}

        if (needTags[keyNameValue] && needTags[keyNameValue] !== '0') {
            var $flyoutBold = $flyout.find("span a.c" + needTags[keyNameValue]);
            $flyoutBold.replaceWith("<b>" + $flyoutBold.text() + "</b>");
        } else {
            var $flyoutHidden = $flyout.find("span a.c-delete");
            $flyoutHidden.parent().remove();
        }
    },

    mouseLeave: function (event, self) {
        var $flyout = jQuery('body').find(".flyout");
		// delay the popup kill to see if we just wandered into the flyout
		setTimeout( function() {
			if (!$flyout.hasClass('keepflyoutalive')) {
				// no class so kill this
				$flyout.remove();
			}
		}, 60);
    },

    // calculate the position to place the popout
    calcMousePosition: function (event, flyoutElement) {
        var flyoutDimensions = {
            width: flyoutElement.outerWidth(),
            height: flyoutElement.outerHeight()
        };
        var flyoutCaretCenter = 26;
        var correction = -2;

		// changed this based on recommendation by Remy Sharp - he also says we should try jquery plugins for this but none seem to do what we need
		// http://codylindley.com/blogstuff/js/jtip/
		// http://docs.jquery.com/Plugins/Tooltip

        // this will be the absolute position relative to the document
		var topOffset = event.pageY - correction;
		var leftOffset = event.pageX - flyoutCaretCenter;
        var newPosition = {
            top: topOffset,
            left: leftOffset
        };
		
		// this attempts to ensure the popout is not off the screen
        var totalWidth = jQuery(window).width();
        if ((newPosition.left - flyoutCaretCenter + flyoutDimensions.width) >= totalWidth) {
            newPosition.left = totalWidth - flyoutDimensions.width;
        }
        return newPosition;
    },

    // calculate the position to place the popout
    calcPosition: function (containerElement, flyoutElement) {
        var containerDimensions = {
            width: containerElement.outerWidth(),
            height: containerElement.height()
        };
        var flyoutDimensions = {
            width: flyoutElement.outerWidth(),
            height: flyoutElement.outerHeight()
        };
        
        var flyoutCaretCenter = 26;
        // this will be the position relative to the containerElement
        var newPosition = {
            left: ((containerDimensions.width / 2) - flyoutCaretCenter) + "px",
            top: (containerDimensions.height - 2) + "px"
        };

		// this attempts to ensure the popout is not off the screen
        var containerOffset = containerElement.offset();
        var totalWidth = jQuery(window).width();
        if ((containerOffset.left + flyoutDimensions.width) >= totalWidth) {
            newPosition.left = totalWidth - (containerOffset.left + flyoutDimensions.width) - flyoutCaretCenter;
        }
        return newPosition;
    }
});

// run this on page load
jQuery(function() {
	// Add selected class to parent li.
	jQuery('ul.radios input').click(GenericFormEvents.selectRadioLi);
	
	// Toggle default input values
	jQuery('input[type=text][title]').defaultText();
	
	// load up the tags flyout handling
	if (typeof TagFlyout !== 'undefined') {
	    var pageTagFlyout = new TagFlyout();
	}

	if (typeof TagFlyoutPopup !== 'undefined') {
	    var pageTagFlyoutPopup = new TagFlyoutPopup();
	}
    
    // activate "Return to Top" link
    jQuery('#return_to_top').click(function() {
		scroll(0,0);
        return false;
    });
    
    E360.initButtonLinks();
    E360.initSchoolSwitcher();


	// Handle the admin_upgrade_alert
	jQuery('#upgrade_alert a.close').bind('click', function(event) {
		jQuery.ajax({
			type: 'POST',
			url: event.target.href,
			success: function(data, textStatus, request) {
				jQuery('#upgrade_alert').fadeOut('fast');
			}
		});
		return false;
	});
});
