﻿/// <reference path="jquery-1.4.1.js"/>
/// <reference path="jquery.validate-1.6.js"/>

function InitExpandCollapseCell(triggerCellId, rowToCollapseId, name)
{
	/// <summary>
	/// Sets up the cell specified by the triggerCellId ID so that when clicked it will cause the row
	/// with the ID specified by rowToCollapseId to toggle disappear and appear. The trigger cell
	/// must have one of the CSS classes "Expand" or "Collapse" depending on whether, when clicked,
	/// it will collapse or expand that second row. If the class is "Expand" the second row will 
	/// automatically be hidden for you.
	/// </summary>
	/// <param name="triggerCellId">The ID of the cell you want to set up as the toggle button</param>
	/// <param name="rowToCollapseId">The ID of the row to collapse/expand</param>
	/// <param name="name">The name of the thing being hidden and shown (for tooltip)</param>
	$(function()
	{

		var cell = $("#" + triggerCellId);
		var rowToCollapse = $("#" + rowToCollapseId);

		function ChangeTooltip(cell)
		{
			if (cell.hasClass("Collapse"))
				cell.attr("title", "Hide " + name);
			else
				cell.attr("title", "Show " + name);
		}

		//Set the tooltip
		ChangeTooltip(cell);

		//Intitally hide the second row, if necessary
		if (cell.hasClass("Expand"))
			rowToCollapse.hide();

		//Set up what happens on click
		cell.click(function()
		{
			rowToCollapse.toggle();
			if (cell.hasClass("Expand"))
			{
				cell.removeClass("Expand");
				cell.addClass("Collapse");
			}
			else
			{
				cell.removeClass("Collapse");
				cell.addClass("Expand");
			}
			ChangeTooltip(cell);
		});
	});
}


function MsJsonDateToDateObject(jsonStr)
{
	/// <summary>
	///	Converts the Microsoft JSON date string into a JavaScript Date object
	/// </summary>
	/// <param name="jsonStr" type="String">The JSON date string</param>
	/// <returns type="Date">The date object or null if there was parse error</returns>
	
	var regexp = /\/Date\((\d+)\)\//i;

	var results = regexp.exec(jsonStr);
	if (results == null)
		return null;

	return new Date(parseInt(results[1]));
}


function ValidateAustralianDate(dateStr)
{
	/// <summary>
	///	Validates a short Australian date string (dd/mm/yyyy) and makes sure its a valid date
	/// </summary>
	/// <param name="dateStr" type="String">The date string</param>
	/// <returns type="Boolean">True if its valid, false otherwise</returns>
	
	var regexp = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/i;

	if (dateStr == null || dateStr == "")
		return true; //We don't validate requiredness

	var results = regexp.exec(dateStr);
	if (results == null)
		return false;

	return ValidateStrictDate(results[1], results[2], results[3]);
}

//Install the validator into jQuery.Validate
$.validator.addMethod("australianDate", function(value, element)
{
	return ValidateAustralianDate(value);
},
"Please enter a valid date in the format dd/mm/yyyy.");


function ValidateStrictDate(day, month, year)
{
	/// <summary>
	///	Strictly validates a date and makes sure its a valid one
	/// </summary>
	/// <param name="day" type="int">The day</param>
	/// <param name="month" type="int">The month</param>
	/// <param name="year" type="int">The year</param>
	/// <returns type="Boolean">True if its valid, false otherwise</returns>

	if (day < 1)
		return false;

	if (month == 2)
	{
		var isLeapYear = year % 400 == 0 || year % 4 == 0 && year % 100 != 0;
		if (isLeapYear)
			return day <= 29;
		else
			return day <= 28;
	}
	else if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12)
		return day <= 31;
	else if (month == 4 || month == 6 || month == 9 || month == 11)
		return day <= 30;
	else
		return false;
}


function FormatAwayFromZeroDecimal(number, precision, digitGroup)
{
	/// <summary>
	///	Rounds (using away from zero rounding) and formats a number so that it
	/// has a specified number of decimal places
	/// </summary>
	/// <param name="number" type="float">The number to be formatted</param>
	/// <param name="precision" type="int">The number of decimal places to have</param>
	/// <param name="digitGroup" type="Boolean">True if you want digit grouping, false otherwise</param>
	/// <returns type="String">The formatted number string</returns>

	number = number.toString().replace(",", ""); //Strip digit grouping
	if (parseFloat(number) == NaN)
		return NaN;
		
	number = RoundAwayFromZeroDecimal(number, precision);
	var numberStr = number.toString();
	
	if (precision == 0)
		return numberStr;
	
	var dotIndex = numberStr.lastIndexOf(".");

	var decimalStr;
	if (dotIndex == -1)
		decimalStr = "";
	else
		decimalStr = numberStr.substring(dotIndex + 1);

	var neededZeroes = precision - decimalStr.length;
	for (i = 0; i < neededZeroes; i++)
	{
		decimalStr += "0";
	}

	var isNegative = number < 0;
	var wholeNumberStr = Math.truncate(Math.abs(number)).toString();

	if (digitGroup)
	{
		var highestIndex = wholeNumberStr.length - 1;
		for (i = wholeNumberStr.length - 1; i >= 0; i--)
		{
			var modCheck = highestIndex - i + 1;
			if (i != 0 && modCheck % 3 == 0)
				wholeNumberStr = wholeNumberStr.insert(",", i);
		}
	}

	if (isNegative)
		wholeNumberStr = "-" + wholeNumberStr;
		
	return wholeNumberStr + "." + decimalStr;
}

function RoundAwayFromZeroDecimal(number, precision)
{
	/// <summary>
	///	Rounds (using away from zero rounding) a number so that it
	/// has a specified number of decimal places
	/// </summary>
	/// <param name="number" type="float">The number to be formatted</param>
	/// <param name="precision" type="int">The number of decimal places to have</param>
	/// <returns type="float">The rounded number</returns>

	var isNegative = number < 0;

	if (isNegative)
		number *= -1;

	var roundedNum = Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision);

	if (isNegative)
		roundedNum *= -1;

	return roundedNum;
}


Math.truncate = function(number)
{
	/// <summary>
	///	Truncates a floating point number
	/// </summary>
	/// <param name="number" type="float">The number to be truncated</param>
	/// <returns type="float">The truncated number</returns>

	if (number > 0)
		return Math.floor(number);
	else
		return Math.ceil(number);
}

String.prototype.insert = function(str, index)
{
	/// <summary>
	/// Inserts a string at the specified position in a string and returns the result
	/// <summary>
	/// <param name="str" type="String">The string to insert into</param>
	/// <param name="index" type="int">The index to insert at</param>
	return this.substr(0, index) + str + this.substr(index);
}


function ConfigureFormWithXvalValidationSettings(formElement)
{
	/// <summary>
	/// Copies xVal's _ensureFormIsMarkedForValidation method and sets up the correct validation
	/// styling
	/// <summary>
	/// <param name="formElement" type="jQuery">The form element selected in a jQuery object</param>

	formElement.validate(
	{
		errorClass: "field-validation-error",
		errorElement: "span",
		highlight: function(element) { $(element).addClass("input-validation-error"); },
		unhighlight: function(element) { $(element).removeClass("input-validation-error"); }
	});
}


function SilverlightErrorHandler(sender, args)
{
	/// <summary>
	/// Handles errors from Silverlight controls
	/// </summary>

	var appSource = "";
	if (sender != null && sender != 0)
		appSource = sender.getHost().Source;

	var errorType = args.ErrorType;
	var iErrorCode = args.ErrorCode;

	if (errorType == "ImageError" || errorType == "MediaError")
		return;

	var errMsg = "Unhandled Error in Silverlight Application " + appSource + "\n";

	errMsg += "Code: " + iErrorCode + "    \n";
	errMsg += "Category: " + errorType + "       \n";
	errMsg += "Message: " + args.ErrorMessage + "     \n";

	if (errorType == "ParserError")
	{
		errMsg += "File: " + args.xamlFile + "     \n";
		errMsg += "Line: " + args.lineNumber + "     \n";
		errMsg += "Position: " + args.charPosition + "     \n";
	}
	else if (errorType == "RuntimeError")
	{
		if (args.lineNumber != 0)
		{
			errMsg += "Line: " + args.lineNumber + "     \n";
			errMsg += "Position: " + args.charPosition + "     \n";
		}
		errMsg += "MethodName: " + args.methodName + "     \n";
	}

	throw new Error(errMsg);
}


var Onset = {};
Onset.Ajax = {};

(function() //Defines a private scope for the functions
{
	Onset.GetInheritedBackgroundColour = function(jQueryElement)
	{
		/// <summary>
		/// Gets the inherited background colour of the specified jQuery element. Note that
		/// this only works if each element in the hierarchy has not been displaced so that it 
		/// no longer sits on top of its parent.
		/// </summary>
		/// <param name="jQueryElement" type="jQuery">The jQuery element to get the colour for</param>

		var colour = jQueryElement.css("background-color");

		if (colour !== 'rgba(0, 0, 0, 0)' && colour !== 'transparent')
			return colour;

		if (jQueryElement.is("body"))
			return false;
		else
			return Onset.GetInheritedBackgroundColour(jQueryElement.parent());
	};


	Onset.RgbToHex = function(rgb)
	{
		/// <summary>
		/// Converts an rgb colour to a hex colour. ie rgb(255,255,255) -> #FFFFFF
		/// </summary>
		/// <param name="rgb" type="String">The rgb string</param>
		/// <returns type="String">The hex string</param>
	
		if (rgb.search("rgb") == -1)
			return rgb;
		else
		{
			rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/);
			function hex(x)
			{
				return ("0" + parseInt(x).toString(16)).slice(-2);
			}
			return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
		}

	};


	Onset.ConfigureDatePicker = function(textboxes)
	{
		/// <summary>
		/// Sets up the date picker on the specified text boxes and sets up the extra validation
		/// </summary>
		/// <param name="textboxes" type="jQuery">The textboxes to configure</param>

		textboxes.datepicker({ dateFormat: "dd/mm/yy", changeYear: true, changeMonth: true })
		         .rules("add", { "australianDate": true });
	};


	Onset.ConfigureNumberFormatting = function(fields, decimalPlaces)
	{
		/// <summary>
		/// Configures the number formatters that format number input
		/// </summary>
		/// <param name="textboxes" type="jQuery">The textboxes to configure</param>
		/// <param name="decimalPlaces" type="Number">The number of decimal places to round to</param>

		var func = function()
		{
			var field = $(this);
			if (field.valid())
				field.val(FormatAwayFromZeroDecimal(field.val(), decimalPlaces, false));
		};

		fields.blur(func);
	};


	Onset.ConfigureViewIconOffDropdown = function(viewIconUrl, title, iconInsertionFunc, assocDropdown, viewUrlCreationFunc, openInNewWindow)
	{
		/// <summary>
		/// Creates and configures a view
		/// </summary>
		/// <param name="request" type="XMLHttpRequest">
		/// The XMLHttpRequest object used for the AJAX request
		/// </param>
		/// <param name="textStatus" type="String">The jQuery AJAX request status string</param>
		/// <param name="errorThrown">The thrown error or null</param>

		if (openInNewWindow)
			title = title + " (opens in a new window)";

		//Create and insert icon
		var img = $("<img/>").attr("id", assocDropdown.attr("id") + "ViewIcon")
		                     .addClass("InlineIcon")
		                     .attr("src", viewIconUrl)
		                     .attr("alt", title)
		                     .attr("title", title)
		                     .css("cursor", "pointer")
		                     .css("display", "inline");
		iconInsertionFunc(img);

		//Set up click handler
		img.click(function()
		{
			var dropdownVal = $(assocDropdown).val();
			if (dropdownVal == "" || dropdownVal == null)
				return;
			if (openInNewWindow)
				window.open(viewUrlCreationFunc());
			else
				window.location.href = viewUrlCreationFunc();
		});

		//Handle null values in dropdown
		var onChangeFunc = function()
		{
			var dropdownVal = $(assocDropdown).val();
			if (dropdownVal == "" || dropdownVal == null)
				img.hide();
			else
				img.show();
		};
		onChangeFunc();
		assocDropdown.change(onChangeFunc);
	};


	function CreateUrlFromFormatString(urlFormatStr, data)
	{
		/// <summary>
		/// Replaces tokens (string surrounded in $s eg $id$) with
		/// </summary>
		/// <param name="urlFormatStr" type="string">
		/// A URL string with the $id$ token placed where the ID value should be substituted
		/// </param>
		/// <param name="data" type="object">
		/// Object dictionary that contains key/value pairs of tokens (without $s) and the value to 
		/// substitute in their place
		/// </param>
		/// <returns>null</returns>

		for (var i in data)
		{
			urlFormatStr = urlFormatStr.replace("$" + i + "$", data[i]);
		}

		return urlFormatStr;
	}


	Onset.CreateMakeUrlByReplacingIdWithElementValFunc = function(urlFormatStr, formElement)
	{
		/// <summary>
		/// Returns a function that uses the provided urlFormatStr and replaces the $id$ token
		/// with the formElement's value
		/// </summary>
		/// <param name="urlFormatStr" type="string">
		/// A URL string with the $id$ token placed where the ID value should be substituted
		/// </param>
		/// <param name="formElement" type="jQuery">
		/// The form element from which to take the value
		/// </param>
		/// <returns>Function that creates a URL</returns>

		return function() { return CreateUrlFromFormatString(urlFormatStr, { id: formElement.val() }); };
	};


	Onset.CreateAppendInNextCellFunc = function(itemInCell)
	{
		/// <summary>
		/// Creates a function that when passed a jQuery object the object will be appended
		/// inside the cell that is next (on the right) of the cell that contains itemInCell.
		/// </summary>
		/// <param name="itemInCell" type="jQuery">
		/// A jQuery object selecting an item that is contained within the cell that is on the
		/// left of the cell you want to insert into
		/// </param>
		/// <returns>Function that when passed a jQuery object inserts it</returns>

		return function(itemToInsert) { $(itemInCell.parents("td").get(0)).next().append(itemToInsert) };
	};

	Onset.Ajax.OnAjaxError = function(request, textStatus, errorThrown)
	{
		/// <summary>
		/// Should be called when there is an jQuery AJAX error, and will show a message box with 
		/// some debugging info
		/// </summary>
		/// <param name="request" type="XMLHttpRequest">
		/// The XMLHttpRequest object used for the AJAX request
		/// </param>
		/// <param name="textStatus" type="String">The jQuery AJAX request status string</param>
		/// <param name="errorThrown">The thrown error or null</param>

		var msg = "Unexpected ajax request error! You probably want to refresh the page and try again (note that you may lose your input, depending on your browser).";
		msg += "\r\njQuery Status: " + textStatus;
		msg += "\r\nRequest Status: " + request.status + " " + request.statusText;
		msg += "\r\nRequest URL: " + this.url;
		msg += "\r\nRequest Data: " + this.data;
		alert(msg);
	};


	Onset.Ajax.ConfigureOnChangeAjaxLoading = function(onChangeFormElement, updateFormElements, onChangeFormElementInitialVal, urlToUseFunc, dataCreationFunc, loadingImgUrl, onAjaxSuccessFunc, onAjaxCompleteFunc)
	{
		/// <summary>
		/// Configures the reloading of a form element when another form element changes
		/// </summary>
		/// <param name="onChangeFormElement" type="jQuery">
		/// The form element whose change event should be trigger the update
		/// </param>
		/// <param name="updateFormElements" type="jQuery">
		/// The form elements that should be updated with the results of the AJAX request
		/// </param>
		/// <param name="onChangeFormElementInitialVal" type="jQuery">
		/// The ID that the onChangeFormElement was set to when the page was rendered. This is used to
		/// trigger the reloading of the updateFormElements if the browser changes the selection in
		/// the onChangeFormElement right after loading the page (restoring user input from history)
		/// </param>
		/// <param name="urlToUseFunc" type="function">
		/// A function that returns the URL to use
		/// </param>
		/// <param name="dataCreationFunc" type="function">
		/// A function that returns the data to use in the AJAX query
		/// </param>
		/// <param name="loadingImgUrl" type="string">
		/// The URL to the image to use for displaying the loading status
		/// </param>
		/// <param name="onAjaxSuccessFunc" type="function">
		/// The function to call when the AJAX request successfully completes
		/// </param>
		/// <param name="onAjaxCompleteFunc" type="function">
		/// The function to call when the AJAX request completes (success or fail) (or null)
		/// </param>

		//Create the loading image
		$("<img/>").attr("class", onChangeFormElement.attr("id") + "AjaxLoading")
                   .attr("title", "Loading...")
                   .attr("alt", "Loading...")
                   .attr("src", loadingImgUrl)
                   .css("display", "inline")
                   .hide()
                   .insertAfter(updateFormElements);
		var loadingImages = $("img." + onChangeFormElement.attr("id") + "AjaxLoading");

		var ajaxFunc = function()
		{
			var entityId = onChangeFormElement.val();
			if (entityId == "" || entityId == null)
				return;

			loadingImages.show();

			jQuery.ajax(
			{
				url: urlToUseFunc(),
				type: "POST",
				success: function(data, request, textStatus) { onAjaxSuccessFunc(data, updateFormElements, request, textStatus); },
				complete: function(request, textStatus)
				{
					loadingImages.hide();
					if (onAjaxCompleteFunc != null)
						onAjaxCompleteFunc(request, textStatus);
				},
				error: Onset.Ajax.OnAjaxError,
				dataType: "json",
				data: dataCreationFunc()
			});
		};

		//Don't reload the contents of the updateFormElements if the onChangeFormElement
		//hasn't been changed since the page loaded (the browser might change it
		//right after loading to restore user input values after a refresh)
		var entityId = onChangeFormElement.val();
		if (onChangeFormElement.is(":checkbox") && onChangeFormElement.is(":checked") == false)
			entityId = false;
		if (entityId != "" && entityId != null && entityId != onChangeFormElementInitialVal)
			ajaxFunc();

		onChangeFormElement.change(ajaxFunc);
	};

	Onset.Ajax.NullDataCreationFunc = function()
	{
		/// <summary>
		/// Function that can be used for the dataCreationFunc parameter on the ConfigureOnChangeAjaxLoading
		/// method. This returns null for the data.
		/// </summary>
		/// <returns type="object">null</returns>

		return null;
	};

	Onset.Ajax.CreateDataFuncUsingFormElementsIdAndValue = function(formElements)
	{
		/// <summary>
		/// Function that can be used for the dataCreationFunc parameter on the ConfigureOnChangeAjaxLoading
		/// method. This returns an object used like a dictionary where the keys are the specified formElements'
		/// IDs and the values are the formElements' values.
		/// </summary>
		/// <param name="formElements" type="jQuery">
		/// A jQuery object containing one or more formElements to get data from
		/// </param>
		/// <returns type="object">
		/// Data object derived from the form elements specified
		/// </returns>

		return function()
		{
			var data = {};

			formElements.each(function(index, element)
			{
				var formElement = $(element);
				if (formElement.is(":checkbox") && formElement.is(":checked") == false)
				{
					data[formElement.attr("id")] = false;
					return;
				}

				data[formElement.attr("id")] = formElement.val();
			});

			return data;
		};
	};

	function OnAjaxDropdownLoadingSuccess(data, updateDropdown, insertBlankFirst, request, textStatus)
	{
		/// <summary>
		/// Called when the dropdown loading AJAX request was successful. Clears updateDropdown and
		/// sets the downloaded options into it.
		/// </summary>
		/// <param name="data" type="Array">An array of Pair objects</param>
		/// <param name="updateDropdown" type="jQuery">
		/// The dropdown that should be updated with the results of the AJAX request
		/// </param>
		/// <param name="insertBlankFirst" type="Boolean">
		/// If true, a blank item is inserted as the first element in the updateDropdown
		/// </param>
		/// <param name="request" type="XMLHttpRequest">
		/// The XMLHttpRequest object used for the AJAX request
		/// </param>
		/// <param name="textStatus" type="String">The jQuery AJAX request status string</param>

		var oldVal = updateDropdown.val();

		$("option", updateDropdown).remove();

		if (insertBlankFirst)
			$("<option/>").appendTo(updateDropdown);

		for (i in data)
		{
			var option = $("<option/>").text(data[i].Second);
			if (data[i].First != null)
				option.val(data[i].First);
			if (data[i].First == oldVal)
				option.attr("selected", "selected");

			updateDropdown.append(option);
		}
	}


	Onset.Ajax.OnAjaxDropdownLoadingSuccess = function(data, updateDropdown, request, textStatus)
	{
		/// <summary>
		/// Called when the dropdown loading AJAX request was successful. Clears updateDropdown and
		/// sets the downloaded options into it.
		/// </summary>
		/// <param name="data" type="Array">An array of Pair objects</param>
		/// <param name="updateDropdown" type="jQuery">
		/// The dropdown that should be updated with the results of the AJAX request
		/// </param>
		/// <param name="request" type="XMLHttpRequest">
		/// The XMLHttpRequest object used for the AJAX request
		/// </param>
		/// <param name="textStatus" type="String">The jQuery AJAX request status string</param>

		OnAjaxDropdownLoadingSuccess(data, updateDropdown, false, request, textStatus);
	};

	Onset.Ajax.OnAjaxDropdownLoadingSuccessInsertBlank = function(data, updateDropdown, request, textStatus)
	{
		/// <summary>
		/// Called when the dropdown loading AJAX request was successful. Clears updateDropdown and
		/// sets the downloaded options into it including a blank option at the beginning.
		/// </summary>
		/// <param name="data" type="Array">An array of Pair objects</param>
		/// <param name="updateDropdown" type="jQuery">
		/// The dropdown that should be updated with the results of the AJAX request
		/// </param>
		/// <param name="insertBlankFirst" type="Boolean">
		/// If true, a blank item is inserted as the first element in the updateDropdown
		/// </param>
		/// <param name="request" type="XMLHttpRequest">
		/// The XMLHttpRequest object used for the AJAX request
		/// </param>
		/// <param name="textStatus" type="String">The jQuery AJAX request status string</param>

		OnAjaxDropdownLoadingSuccess(data, updateDropdown, true, request, textStatus);
	};

})();
