/*
	file:			jquery.profiler.js
	version:		2.0
	author:			sam jackson
*/


(function(jQuery)
{

	// profilerDefaults contains default options for the plugin
	var profilerDefaults =
	{
		toggleSpeed:				80,
		sliderSpeed:				300,
		toggleGrowText:				'Change',
		toggleShrinkText:			'Done',
		submitButtonText:			'Select',
		initiallyShrunk:			false,
		shrinkOnDone:				true,
		snapArrow:					true,
		alsoSnapOnDrag:				false,
		snapSpeed:					100,
		initialProfileIndex:		0,
		topBarTextPrefix:			'CURRENTLY SELECTED: ',
		bottomBarTextPrefix:		'PROFILE DESCRIPTION: ',
		mainBarText:				'',
		uncommittedText:			' BUT NOT YET COMMITTED CHANGE',
		imagePathPrefix:			''
	};
	
	// profilerCallbacks contains default callback functions.
	// all callbacks are performed with a profilerData object as an argument
	var profilerCallbacks =
	{
		onDone:						null,
		onCancel:					null,
		onDrag:						null,
		onSelect:					null,
		onGrow:						null,
		onShrink:					null
	};

	// profilerIdentifiers contains string id's which are to be attached to certain elements
	var profilerIdentifiers =
	{
		topBar:					'top-bar',
		mainBar:				'main-bar',
		bottomBar:				'bottom-bar',
		mainBarText:			'main-bar-text',
		sliderContainer:		'slider-container',
		topBarText:				'top-bar-text',
		toggleButtonContainer:	'toggle-button-container',
		toggleButton:			'toggle-button',
		bottomBarText:			'bottom-bar-text',
		profilesContainer:		'profiles-container',
		sliderLineContainer:	'slider-line-container',
		sliderArrow:			'slider-arrow',
		profileContainerPrefix:	'profile-container-',
		profileNameSpan:		'profile-name-span'
	};

	// profilerClasses contains string classes which are to be given to certain elements
	var profilerClasses =
	{
		topBar:					'top-bar',
		mainBar:				'main-bar',
		bottomBar:				'bottom-bar',
		mainBarText:			'main-bar-text',
		sliderContainer:		'slider-container',
		topBarText:				'top-bar-text',
		toggleButtonContainer:	'toggle-button-container',
		toggleButton:			'toggle-button',
		bottomBarText:			'bottom-bar-text',
		profilesContainer:		'profiles-container',
		sliderLineContainer:	'slider-line-container',
		sliderArrow:			'slider-arrow',
		sliderArrowDragging:	'slider-arrow-dragging',
		profileContainer:		'profile-container',
		profileContainerPrefix:	'profile-container-',
		profileImage:			'profile-image',
		profileText:			'profile-text',
		chosenProfileContainer:	'chosen-profile-container',	
		profileNameSpan:		'profile-name-span'
	};

	// profilerData contains functions used for getting and setting data unique to the plugins parent element
	var profilerData =
	{
		DATA_NAME:				'profilerdata',
		
		// set is used to assign data to the specified element
		set:					function(element, data)
								{
									if (data != undefined)
									{
										var oldData = profilerData.get(element);
										var extendedData = jQuery.extend({}, oldData, data);
										jQuery.data(element.get(0), profilerData.DATA_NAME, extendedData);
										return data;
									}
									return false;
								},
				
		// get is used to return saved data from the specified element				
		get:					function(element)
								{
									return jQuery.data(element.get(0), profilerData.DATA_NAME) || {};
								},
		
		// opts is used to get or set options					
		opts:					function(element, options)
								{
									if (options != undefined) return profilerData.set(element, {opts: options}).opts;
									return profilerData.get(element).opts;	
								},
					
		// clss is used to get or set classes
		clss:					function(element, classes)
								{
									if (classes != undefined) return profilerData.set(element, {clss: classes}).clss;
									return profilerData.get(element).clss;	
								},
								
		// callbacks is used to get or set callbacks
		callbacks:				function(element, callbacks)
								{
									if (callbacks != undefined) return profilerData.set(element, {callbacks: callbacks}).callbacks;
									return profilerData.get(element).callbacks;	
								},
								
		profilesCount:			function(element, count)
								{
									if (count != undefined) return profilerData.set(element, {profilesCount: count}).profilesCount;
									return profilerData.get(element).profilesCount;
								},
								
		// profiles is used to get or set profiles			
		profiles:				function(element, profiles)
								{
									if (profiles != undefined) return profilerData.set(element, {profiles: profiles}).profiles;
									return profilerData.get(element).profiles;
								},
				
		// dragging is used to get or set the handles current drag status				
		dragging:				function(element, dragging)
								{
									if (dragging != undefined) return profilerData.set(element, {dragging: dragging}).dragging;
									return profilerData.get(element).dragging;
								},
								
		originalProfileIndex:	function(element, index)
								{
									if (index != undefined) return profilerData.set(element, {originalProfileIndex: index}).originalProfileIndex;
									return profilerData.get(element).originalProfileIndex;
								},
								
		// profileIndex is used to get or set the currently chosen index
		profileIndex:			function(element, index)
								{
									if (index != undefined) return profilerData.set(element, {profileIndex: index}).profileIndex;
									return profilerData.get(element).profileIndex;
								},
								
		// toggleState is used to get or set the currently collapse/expand state (true = expanded, false = collapsed)
		toggleState:			function(element, state)
								{
									if (state != undefined) return profilerData.set(element, {toggleState: state}).toggleState;
									return profilerData.get(element).toggleState;
								}
	};
	
	// profilerFind is used to find and return certain elements
	var profilerFind =
	{
		// elementById is used to lookup an item by its id and then return it
		elementById:			function(profiler, identifier)
								{
									return profiler.find("#" + identifier);
								},
							
		// elementsByClass is used to lookup items by class and then return them	
		elementsByClass:		function(profiler, cls)
								{
									return profiler.find("." + cls);	
								},
								
		mainBar:				function(profiler)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.mainBar);
								},
								
		topBar:					function(profiler)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.topBar);
								},
								
		topBarText:				function(profiler)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.topBarText);
								},
								
		bottomBar:				function(profiler)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.bottomBar);
								},
								
		bottomBarText:			function(profiler)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.bottomBarText);
								},
								
		toggleButton:			function(profiler)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.toggleButton);
								},
								
		sliderLineContainer:	function(profiler)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.sliderLineContainer);
								},
								
		sliderArrow:			function(profiler)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.sliderArrow);
								},
								
		profilesContainer:		function(profiler)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.profilesContainer);
								},
								
		allProfileContainers:	function(profiler)
								{
									return profilerFind.profilesContainer(profiler).children('div');
								},
								
		profileContainer:		function(profiler, profileIndex)
								{
									return profilerFind.elementById(profiler, profilerIdentifiers.profileContainerPrefix + profileIndex);
								}
	};
	
	// profilerCreate is used to create and return new elements with some default settings: id, class, css, children
	var profilerCreate =
	{
		clearDiv:				function()
								{
									return $('<div></div>')
										.css('clear', 'both');	
								},
		
		topBar:					function(classes)
								{
									return $("<div></div>")
										.attr('id', profilerIdentifiers.topBar)
										.addClass(classes.topBar);
								},
								
		mainBar:				function(classes)
								{
									return $("<div></div>")
										.attr('id', profilerIdentifiers.mainBar)
										.addClass(classes.mainBar);
								},
								
		bottomBar:				function(classes)
								{
									return $("<div></div>")
										.attr('id', profilerIdentifiers.bottomBar)
										.addClass(classes.bottomBar);
								},
								
		mainBarText:			function(classes, text)
								{
									return $("<p></p>")
										.attr('id', profilerIdentifiers.mainBarText)
										.addClass(classes.mainBarText)
										.html(text);
								},
								
		sliderContainer:		function(classes)
								{
									return $("<div></div>")
										.attr('id', profilerIdentifiers.sliderContainer)
										.addClass(classes.sliderContainer);
								},
								
		topBarText:				function(classes, text)
								{
									return $("<p></p>")
										.attr('id', profilerIdentifiers.topBarText)
										.addClass(classes.topBarText)
										.html(text);
								},
								
		toggleButtonContainer:	function(classes)
								{
									return $("<div></div>")
										.attr('id', profilerIdentifiers.toggleButtonContainer)
										.addClass(classes.toggleButtonContainer);
								},								
								
		toggleButton:			function(classes, text)
								{
									return $("<a></a>")
										.attr('id', profilerIdentifiers.toggleButton)
										.addClass(classes.toggleButton)
										.text(text);
								},
								
		bottomBarText:			function(classes, text)
								{
									return $("<p></p>")
										.attr('id', profilerIdentifiers.bottomBarText)
										.addClass(classes.bottomBarText)
										.html(text);
								},
								
		profilesContainer:		function(classes)
								{
									return $("<div></div>")
										.attr('id', profilerIdentifiers.profilesContainer)
										.addClass(classes.profilesContainer);
								},
								
		sliderLineContainer:	function(classes)
								{
									return $("<div></div>")
										.attr('id', profilerIdentifiers.sliderLineContainer)
										.addClass(classes.sliderLineContainer);
									},
									
		sliderArrow:			function(classes)
								{
									return $("<div></div>")
										.css('position', 'relative')
										.attr('id', profilerIdentifiers.sliderArrow)
										.addClass(classes.sliderArrow);
								},
								
		profileContainer:		function(classes, profileIndex, profileWidth)
								{
									return $("<div></div>")
										.css('width', profileWidth)
										.attr('id', profilerIdentifiers.profileContainerPrefix + profileIndex)
										.addClass(classes.profileContainerPrefix + profileIndex)
										.addClass(classes.profileContainer);
								},
								
		profileImage:			function(classes, src)
								{
									return $("<img></img>")
										.addClass(classes.profileImage)
										.attr('src', src);
								},
								
		profileText:			function(classes, text)
								{
									return $("<p></p>")
										.addClass(classes.profileText)
										.html(text);
								},
								
		profileNameSpan:		function(classes, text)
								{
									return $("<span></span>")
										.attr('id', profilerIdentifiers.profileNameSpan)
										.addClass(classes.profileNameSpan)
										.html(text);
								}
	};
	
	// profilerBinds contains functions used in binds and all accept the event argument
	var profilerBinds =
	{										
		toggleHeight:					function(ev)
										{
											ev.preventDefault();
											var profiler = ev.data.profiler;
											var options = ev.data.options;
											var toggleButton = ev.data.toggleButton;
											
											// if the button was clicked to comit the changes then perform the done function
											if (toggleButton.text() === options.toggleShrinkText) done(profiler);
											else toggleHeight(profiler, options, toggleButton);
										},
										
		beginDrag:						function(ev)
										{
											ev.preventDefault();
											var profiler = ev.data.profiler;
											var options = ev.data.options;
											var classes = ev.data.classes;
											var sliderArrow = ev.data.sliderArrow;
											var sliderLineContainer = ev.data.sliderLineContainer;
											var clickedPos = parseInt(ev.pageX) || 0;
											beginDrag(profiler, options, classes, sliderArrow, sliderLineContainer, clickedPos);
										},
										
		endDrag:						function(ev)
										{
											ev.preventDefault();
											var profiler = ev.data.profiler;
											var options = ev.data.options;
											var classes = ev.data.classes;
											var sliderArrow = ev.data.sliderArrow;
											endDrag(profiler, options, classes, sliderArrow);
										},
										
		dragArrow:						function(ev)
										{
											ev.preventDefault();
											var profiler = ev.data.profiler;
											var options = ev.data.options;
											var sliderArrow = ev.data.sliderArrow;
											var sliderLineContainer = ev.data.sliderLineContainer;
											var clickedPos = ev.data.clickedPos;
											var currentPos = parseInt(ev.pageX) || 0;
											var startingLeft = ev.data.startingLeft;
											dragArrow(profiler, options, sliderArrow, sliderLineContainer, clickedPos, currentPos, startingLeft);
										},
										
		moveArrow:						function(ev)
										{
											ev.preventDefault();
											var profiler = ev.data.profiler;
											var options = ev.data.options;
											var sliderArrow = ev.data.sliderArrow;
											var sliderLineContainer = ev.data.sliderLineContainer;
											var clickedPos = parseInt(ev.pageX) || 0;
											moveArrowByClick(profiler, options, sliderArrow, sliderLineContainer, clickedPos);
										},
										
		moveTo:							function(ev)
										{
											ev.preventDefault();
											var profiler = ev.data.profiler;
											var options = ev.data.options;
											var profileIndex = ev.data.index;					
											moveToProfile(profiler, options, profileIndex, options.sliderSpeed);
										}
	};

	// this is the starting point of the plugin
	jQuery.fn.profiler = function(command, args)
	{
		var profiler = $(this.get(0));
		
		// the following commands can only be performed on one element at a time as they return a value.
		// So if multiple elements are passed only the first will be affected
		if (typeof(command) == 'string')
		{			
			switch (command)
			{
				
			}
		}

		// loop through each of the passed elements and then return them for chaining
		return this.each(function()
		{
			profiler = $(this);
			
			// determine what command was given and act on it appropriately
			
			if (typeof(command) == 'string')
			{
				switch (command)
				{
					case 'initialize': case 'init':
						initialize(profiler, args.profiles, args.options, args.classes, args.callbacks);
						break;
					case 'cancel':
						cancel(profiler);
						break;
					case 'done':
						done(profiler);
						break;
					case 'grow': case 'open':
						growProfiler(profiler);
						break;
					case 'goto': case 'moveto': case 'select':
						moveToProfile(profiler, undefined, args);
						break;
				}
			}
		});
	};
	
	// initialize is used to setup the specified container as a profiler display, it will build
	// child elements, position and populate them.
	function initialize(profiler, profiles, options, classes, callbacks)
	{		
		// extend the default classes with the specified classes and then store them
		classes = profilerData.clss(profiler, jQuery.extend({}, profilerClasses, classes));
		
		// extend the default callbacks with the specified callbacks and then store them
		callbacks = profilerData.callbacks(profiler, jQuery.extend({}, profilerCallbacks, callbacks));
		
		var basicProfiles = profiles;
		
		// load the profiles from the xml file if one is specified
		if (typeof(profiles) == 'string') basicProfiles = readXML(profiles);
		
		// store the profiles
		profilerData.profiles(profiler, basicProfiles);
		
		// extend the default options with the specified options and then store them
		options = profilerData.opts(profiler, jQuery.extend({}, profilerDefaults, options));
		
		// if the specified initial profile index is actually a string, assume its a tag and retrieve the appropriate id for it
		if (typeof(options.initialProfileIndex) === 'string') options.initialProfileIndex = getIndexWithTag(profiler, options.initialProfileIndex, basicProfiles);
		
		// store the number of profiles
		var profileCount = profilerData.profilesCount(profiler, basicProfiles.length);
		
		// initially fix the size of the profiler to prevent it growing and shrinking unwantedly during creation
		profiler.css('height', profiler.height());
		
		// create the elements for the profiler
		createElements(profiler, options, classes);
		
		// create the profiles
		createProfiles(profiler, options, classes, basicProfiles);
		
		// bind the elements
		bindElements(profiler, options, classes);
		
		// save the selected index
		profilerData.originalProfileIndex(profiler, options.initialProfileIndex);
		
		// move to the initial profile
		moveToProfile(profiler, options, options.initialProfileIndex, 0, profileCount);
		
		// set initial toggle state of profiler
		toggleHeight(profiler, options, undefined, !options.initiallyShrunk, 0);
		setTimeout(
			function()
			{
				// this is an attempt to prevent a bug which changes the height before all of the elements have been
				// added to the dom
				toggleHeight(profiler, options, undefined, !options.initiallyShrunk, 0);
			}, 200);
	};
	
	// getIndexWithTag returns the index of the profile with the specified tag
	function getIndexWithTag(profiler, tag, basicProfiles)
	{
		if (basicProfiles === undefined) basicProfiles = profilerData.profiles(profiler);
		
		var found = false;
		var count = 0;
		jQuery.each(basicProfiles, function()
		{
			if (found === false)
			{
				var profile = $(this).get(0);
				if (profile.TAG === tag) found = count;
				count++;
			}
		});
		return (found === false) ? 0 : found;
	};
	
	// getTagWithIndex returns the tag of the profile with the specified index
	function getTagWithIndex(profiler, index, basicProfiles)
	{
		if (basicProfiles === undefined) basicProfiles = profilerData.profiles(profiler);
		var profile = basicProfiles[index];
		if (profile === undefined) return '';
		return profile.TAG;
	};
	
	// createElements is used to create all of the child containers needed to layout the profiler display
	function createElements(profiler, options, classes)
	{		
		// create and append the top bar
		var topBar = profilerCreate.topBar(classes).appendTo(profiler);
		
		// create and append the main bar
		var mainBar = profilerCreate.mainBar(classes).appendTo(profiler);
		
		// create and append the bottom bar
		var bottomBar = profilerCreate.bottomBar(classes).appendTo(profiler);
		
		// create and append the main bar text
		var mainBarText = profilerCreate.mainBarText(classes, options.mainBarText).appendTo(mainBar);
		
		// create and append  the slider container
		var sliderContainer = profilerCreate.sliderContainer(classes).appendTo(mainBar);
		
		// create and append the topBar text
		var topBarText = profilerCreate.topBarText(classes, options.topBarTextPrefix).appendTo(topBar);
		
		// create and append the toggle button container
		var toggleButtonContainer = profilerCreate.toggleButtonContainer(classes).appendTo(topBar);
		
		// create and append the toggle button
		var toggleButton = profilerCreate.toggleButton(classes, 'toggle').appendTo(toggleButtonContainer);
		
		// create and append the profiles container
		var profilesContainer = profilerCreate.profilesContainer(classes).appendTo(sliderContainer);
		
		// create and append the slider line container
		var sliderLineContainer = profilerCreate.sliderLineContainer(classes).appendTo(sliderContainer);
		
		// create and append the bottomBar text
		var bottomBarText = profilerCreate.bottomBarText(classes, options.bottomBarTextPrefix).appendTo(bottomBar);
		
		// create and append the slider arrow
		var sliderArrow = profilerCreate.sliderArrow(classes).appendTo(sliderLineContainer);
		
		// add a clear div to the top bar
		profilerCreate.clearDiv().appendTo(topBar);
		
		// add a clear div to the main bar
		profilerCreate.clearDiv().appendTo(mainBar);
		
		// add a clear div to the bottom bar
		profilerCreate.clearDiv().appendTo(bottomBar);
	};
	
	// createProfiles creates the graphical representation of each profile
	function createProfiles(profiler, options, classes, basicProfiles)
	{
		var profilesContainer = profilerFind.profilesContainer(profiler);
		var profilesArray = [];
		
		// determine how wide the profiles can be
		var profileCount = basicProfiles.length;
		var profilerContainerWidth = profilesContainer.width();
		var profileWidth = Math.round(profilerContainerWidth / profileCount);
		
		// ensure that the combined width is not greater than the container width
		if (profileWidth * profileCount > profilerContainerWidth) profileWidth -= 1;

		// loop through the passed profiles and create its graphical representation
		for (var i = 0; i < profileCount; i++)
		{
			profilesArray[profilesArray.length] = createProfile(profiler, options, classes, basicProfiles, i, profilesContainer, profileWidth);
		}
		
		// add a clear div to the profiles container
		profilerCreate.clearDiv().appendTo(profilesContainer);
	};
	
	// createProfile will create and append the elements used to display a profile
	function createProfile(profiler, options, classes, basicProfiles, profileIndex, profilesContainer, profileWidth)
	{		
		var profileContainer = profilerCreate.profileContainer(classes, profileIndex, profileWidth).appendTo(profilesContainer);
		
		// fix the width of the profile container
		var difference = profileContainer.outerWidth(true) - profileContainer.width();
		profileContainer.width(profileWidth - difference);
		
		// retrieve the profile info
		var info = basicProfiles[profileIndex];
		
		// create the image element for the profile
		var profileImage = profilerCreate.profileImage(classes, options.imagePathPrefix + info.IMAGE).appendTo(profileContainer);
		
		// create the text element for the profile
		var profileText = profilerCreate.profileText(classes, info.NAME).appendTo(profileContainer);
		
		// bind the profile to events
		profileContainer.bind('click', {profiler: profiler, index: profileIndex, options: options}, profilerBinds.moveTo);
	};
	
	// bindElements binds the elements to their respective functions
	function bindElements(profiler, options, classes)
	{
		// bind the top bar toggle button
		var toggleButton = profilerFind.toggleButton(profiler);
		toggleButton.bind('click', {profiler: profiler, options: options, toggleButton: toggleButton}, profilerBinds.toggleHeight);
		
		// bind the slider arrow
		var sliderArrow = profilerFind.sliderArrow(profiler);
		var sliderLineContainer = profilerFind.sliderLineContainer(profiler);
		
		var sliderOptions =
		{
			profiler:				profiler,
			options:				options,
			classes:				classes,
			sliderLineContainer:	sliderLineContainer,
			sliderArrow:			sliderArrow	
		};
		
		sliderLineContainer.bind('click', sliderOptions, profilerBinds.moveArrow);
		sliderArrow.bind('mousedown', sliderOptions, profilerBinds.beginDrag);
	};
	
	// beginDrag initializes the dragging of the slider arrow
	function beginDrag(profiler, options, classes, sliderArrow, sliderLineContainer, clickedPos)
	{
		profilerData.dragging(profiler, true);
		sliderArrow.addClass(classes.sliderArrowDragging);
		
		$(document).bind('mouseup', {profiler: profiler, options: options, sliderArrow: sliderArrow, classes: classes}, profilerBinds.endDrag);
		
		var mouseMoveOptions =
		{
			profiler: 				profiler,
			options:				options,
			clickedPos:				clickedPos,
			sliderArrow:			sliderArrow,
			sliderLineContainer:	sliderLineContainer,
			startingLeft:			(parseInt(sliderArrow.css('left')) || 0)	
		};
		
		$(document).bind('mousemove', mouseMoveOptions, profilerBinds.dragArrow);
	};
	
	// endDrag ends the dragging of the slider arrow
	function endDrag(profiler, options, classes, sliderArrow)
	{
		profilerData.dragging(profiler, false);
		sliderArrow.removeClass(classes.sliderArrowDragging);
		
		$(document).unbind('mouseup', profilerBinds.endDrag);
		$(document).unbind('mousemove', profilerBinds.dragArrow);
		
		if (!(options.snapArrow && options.alsoSnapOnDrag)) moveArrowToProfile(profiler, options, sliderArrow);
	};
	
	// dragArrow is used to physically drag the slider arrow
	function dragArrow(profiler, options, sliderArrow, sliderLineContainer, clickedPos, currentPos, startingLeft)
	{		
		var new_left = startingLeft - (clickedPos - currentPos);
		moveArrow(profiler, options, sliderArrow, sliderLineContainer, new_left, 0, options.snapArrow && options.alsoSnapOnDrag, true);
		
		// perform callback if specified
		var callbacks = profilerData.callbacks(profiler);
		if (callbacks.onDrag !== null)
		{
			var callbackObject = createCallbackObject(profiler);
			callbacks.onDrag(callbackObject);
		}
	};
	
	// moveArrowByClick is used to move the slider arrow to the click position on the slider line container
	function moveArrowByClick(profiler, options, sliderArrow, sliderLineContainer, clickedPos)
	{
		clickedPos -= ((parseInt(sliderLineContainer.offset().left)) || 0);
		var new_left = clickedPos - ((parseInt(sliderArrow.outerWidth(false)) || 0) / 2);
		chosenProfileIndex = moveArrow(profiler, options, sliderArrow, sliderLineContainer, new_left, options.sliderSpeed, options.snapArrow, true);
		
		if (!options.snapArrow) moveArrowToProfile(profiler, options, sliderArrow, chosenProfileIndex);
	};
	
	// moveArrow will simply move the slider arrow to the desired position as long as its within boundries
	function moveArrow(profiler, options, sliderArrow, sliderLineContainer, new_left, speed, snap, display)
	{
		if (speed === undefined) speed = 0;
		if (snap === undefined) snap = options.snapArrow;
		if (display === undefined) display = false;
		
		var min_left = 0;
		var max_left = (parseInt(sliderLineContainer.width()) || 0) - (parseInt(sliderArrow.outerWidth(false)) || 0);
		
		if (new_left > max_left) new_left = max_left;
		else if (new_left < min_left) new_left = min_left;
		
		// save the newly chosen profile
		var chosenProfileIndex = updateChosenProfileIndex(profiler, sliderArrow, new_left);
		
		// determine how the arrow should be moved - if its being snapped then just move straight to the snap pos,
		// else move to where the user clicked
		if (snap)
		{
			moveArrowToProfile(profiler, options, sliderArrow, chosenProfileIndex, speed);
		}
		else
		{
			if (speed === 0) sliderArrow.css('left', new_left); // use this as i imagine it is faster than animate
			else sliderArrow.stop(true, false).animate({left: new_left}, speed);
		}
		
		if (display) displayProfile(profiler, options, chosenProfileIndex);
		
		return chosenProfileIndex;
	};
	
	// determineNearestProfileIndex will return the index of the profile nearest to the specified position
	function updateChosenProfileIndex(profiler, sliderArrow, sliderLeft, profileCount, profilesContainer)
	{
		if (profileCount === undefined) profileCount = profilerData.profilesCount(profiler);
		if (profilesContainer === undefined) profilesContainer = profilerFind.profilesContainer(profiler);
		
		// determine how wide the profiles are
		var profilerContainerWidth = profilesContainer.width();
		var profileWidth = Math.round(profilerContainerWidth / profileCount);
		
		var left = sliderLeft + Math.round(parseInt(sliderArrow.outerWidth()) / 2);
		
		// determine the nerest profiles index
		var index = parseInt(left / profileWidth);
		
		updateChosenProfileClass(profiler, index);
		
		// store and return the index
		return storeChosenProfileIndex(profiler, index);
	};
	
	// storeChosenProfileIndex
	function storeChosenProfileIndex(profiler, index)
	{
		var callbacks = profilerData.callbacks(profiler);
		var currentIndex = profilerData.profileIndex(profiler);
		if (callbacks.onSelect !== null && currentIndex !== index)
		{
			var callbackObject = createCallbackObject(profiler);
			callbacks.onSelect(callbackObject);
		}
		
		// store and return the index
		return profilerData.profileIndex(profiler, index);
	};
	
	// updateChosenProfileClass will make sure that the chosen profile class is on the
	// currently selected profile only.
	function updateChosenProfileClass(profiler, index, classes)
	{
		if (classes === undefined) classes = profilerData.clss(profiler);
		
		var allProfileContainers = profilerFind.allProfileContainers(profiler);
		allProfileContainers.removeClass(classes.chosenProfileContainer);
		
		var profileContainer = profilerFind.profileContainer(profiler, index);
		profileContainer.addClass(classes.chosenProfileContainer);
	};
	
	// moveArrowToProfile will move the arrow to the specific profile index
	function moveArrowToProfile(profiler, options, sliderArrow, profileIndex, speed)
	{
		if (sliderArrow === undefined) sliderArrow = profilerFind.sliderArrow(profiler);
		if (profileIndex === undefined) profileIndex = profilerData.profileIndex(profiler);
		if (speed === undefined) speed = options.snapSpeed;
		
		// retrieve the container of the currently chosen profile
		var profileContainer = profilerFind.profileContainer(profiler, profileIndex);
		
		// determine the center position of the profile container
		var containerOffset = profileContainer.outerWidth(true) * profileIndex;
		var containerCenter = Math.round(profileContainer.width() - profileContainer.width() / 2);
		
		new_left = containerOffset + containerCenter;
		new_left -= Math.round(sliderArrow.outerWidth(false) / 2);
		
		if (options.snapArrow) sliderArrow.stop(true, false);
		
		// move the sliderArrow to the determine position
		sliderArrow.animate({left: new_left}, speed);
	};
	
	// moveToProfile will move the arrow to the selected profile, store the new profile and display its details
	function moveToProfile(profiler, options, profileIndex, speed, profileCount)
	{
		if (options === undefined) options = profilerData.opts(profiler);
		if (profileCount === undefined) profileCount = profilerData.profilesCount(profiler); 
		if (profileIndex < 0) profileIndex = 0;
		else if (profileIndex >= profileCount) profileIndex = profileCount - 1;
		
		storeChosenProfileIndex(profiler, profileIndex)
		moveArrowToProfile(profiler, options, undefined, profileIndex, speed);
		displayProfile(profiler, options, profileIndex);
		updateChosenProfileClass(profiler, profileIndex);
	};
		
	// displayProfile is used to display the details of the specified profile index
	function displayProfile(profiler, options, profileIndex, basicProfiles)
	{
		var classes = profilerData.clss(profiler);
		if (basicProfiles === undefined) basicProfiles = profilerData.profiles(profiler);
		var info = basicProfiles[profileIndex];
		
		var originalIndex = profilerData.originalProfileIndex(profiler);
		
		// update the top bar text
		var committedText = originalIndex === profileIndex ? '' : options.uncommittedText;
		var topBarText = profilerFind.topBarText(profiler);
		topBarText.html(options.topBarTextPrefix);
		var profileNameSpan = profilerCreate.profileNameSpan(classes, info.NAME).appendTo(topBarText);
		topBarText.html(topBarText.html() + committedText);
		
		// update the bottom bar text
		var bottomBarText = profilerFind.bottomBarText(profiler);
		bottomBarText.html(options.bottomBarTextPrefix + info.DESCRIPTION);
	};
	
	// toggleHeight is used to toggle the height of the profiler, either to grow larger or to shrink down
	function toggleHeight(profiler, options, toggleButton, grow, speed)
	{
		if (toggleButton === undefined) toggleButton = profilerFind.toggleButton(profiler);
		if (speed === undefined) speed = options.toggleSpeed;
		if (grow === undefined) grow = profilerData.toggleState(profiler) === false;
		
		if (grow === true) growProfiler(profiler, options, toggleButton, speed);
		else shrinkProfiler(profiler, options, toggleButton, speed);
	};
	
	// growProfiler will grow the profiler to its full size state
	function growProfiler(profiler, options, toggleButton, speed)
	{
		profilerData.toggleState(profiler, true);
		if (options === undefined) options = profilerData.opts(profiler);
		if (toggleButton === undefined) toggleButton = profilerFind.toggleButton(profiler);
		if (speed === undefined) speed = options.toggleSpeed;
		
		var sliderArrow = profilerFind.sliderArrow(profiler);
		var topBar = profilerFind.topBar(profiler);
		var mainBar = profilerFind.mainBar(profiler);
		var bottomBar = profilerFind.bottomBar(profiler);
		var height = (parseInt(topBar.outerHeight(true)) || 0) + (parseInt(mainBar.outerHeight(true)) || 0) + (parseInt(bottomBar.outerHeight(true)) || 0);
		
		profiler.stop(true, false).animate({height: height}, speed,
			function()
			{
				if ($.browser.msie && $.browser.version.substr(0,1) < 8) sliderArrow.animate({opacity: 1}, 0);
			});
			
		toggleButton.text(options.toggleShrinkText);
		
		// perform callback if specified
		var callbacks = profilerData.callbacks(profiler);
		if (callbacks.onGrow !== null)
		{
			var callbackObject = createCallbackObject(profiler);
			callbacks.onGrow(callbackObject);
		}
	};
	
	// shrinkProfiler will shrink the profiler to its shrunken state
	function shrinkProfiler(profiler, options, toggleButton, speed)
	{
		profilerData.toggleState(profiler, false);
		if (options === undefined) options = profilerData.opts(profiler);
		if (toggleButton === undefined) toggleButton = profilerFind.toggleButton(profiler);
		if (speed === undefined) speed = options.toggleSpeed;
		
		var sliderArrow = profilerFind.sliderArrow(profiler);	
		var topBar = profilerFind.topBar(profiler);
		var height = (parseInt(topBar.outerHeight(false)) || 0);
		
		if ($.browser.msie && $.browser.version.substr(0,1) < 8) sliderArrow.css('opacity', 0);
		profiler.stop(true, false).animate({height: height}, speed);
		
		toggleButton.text(options.toggleGrowText);
		
		// perform callback if specified
		var callbacks = profilerData.callbacks(profiler);
		if (callbacks.onShrink !== null)
		{
			var callbackObject = createCallbackObject(profiler);
			callbacks.onShrink(callbackObject);
		}
	};
	
	// cancels any changes made since the profiler was last 'grown'
	function cancel(profiler)
	{
		var callbackObject = createCallbackObject(profiler);
		
		var options = profilerData.opts(profiler);
		var originalProfileIndex = profilerData.originalProfileIndex(profiler);
		moveToProfile(profiler, options, originalProfileIndex, options.sliderSpeed);
		shrinkProfiler(profiler, options, undefined, options.toggleSpeed);
		
		// perform callback if specified
		var callbacks = profilerData.callbacks(profiler);
		if (callbacks.onCancel !== null) callbacks.onCancel(callbackObject);
	};
	
	// commits any changes made since the profiler was last 'grown'
	function done(profiler)
	{
		var callbackObject = createCallbackObject(profiler);
		
		var options = profilerData.opts(profiler);
		
		// save the selected index
		var currentIndex = profilerData.profileIndex(profiler);
		profilerData.originalProfileIndex(profiler, currentIndex);
		
		displayProfile(profiler, options, currentIndex);
		if (options.shrinkOnDone) shrinkProfiler(profiler, options, undefined, options.toggleSpeed);
		
		// perform callback if specified
		var callbacks = profilerData.callbacks(profiler);
		if (callbacks.onDone !== null) callbacks.onDone(callbackObject);
	};
	
	// createCallbackObject will create an object to be passed to a callback function
	function createCallbackObject(profiler)
	{
		var selectedIndex = profilerData.profileIndex(profiler);
		var originalProfileIndex = profilerData.originalProfileIndex(profiler);
		var selectedTag = getTagWithIndex(profiler, selectedIndex);
		
		var callbackObject =
		{
			profiler:		profiler,
			selectedIndex:	selectedIndex,
			originalIndex:	originalProfileIndex,
			changed:		selectedIndex === originalProfileIndex,
			selectedTag:	selectedTag
		};
		
		return callbackObject;
	};
	
	// readXML will read an xml file and return it as profile objects
	function readXML(xmlFile)
	{		
		var returnObjects = [];
		jQuery.ajax(
		{
			type:		'GET',
			url:		xmlFile,
			dataType:	'xml',
			async:		false,
			success:	function(xml)
						{
							// step through each profile within the xml, turn it into an object and add it to
							// an array which is to be returned
							var children = jQuery(xml).children('profiles').children('profile');
							children.each(function()
							{
								returnObjects[returnObjects.length] = xmlToObject($(this));
							});
						}
		});
		return returnObjects;
	};

	// xmlToObject will change the specified profile xml into a profile object
	function xmlToObject(xml)
	{
		var obj =
		{
			NAME:			xml.children('name').text(),
			TAG:			xml.children('tag').text(),
			DESCRIPTION:	xml.children('description').text(),
			IMAGE:			xml.children('image').text()
		};
		return obj;
	};

})(jQuery);