/**
 * general.js
 *
 * This function contains all functions that are generic enough to be used throughout
 * the script.
 */

if (typeof g == "undefined")
  g = {};


/**
 * Bootstrap function. All onload handlers should be added through this function, rather than
 * explicitly set through window.onload (or through the body onload attribute!)
 *
 * TODO deprecate!
 */
g.addOnloadEvent = function(func)
{
	if (window.onload != "function")
		window.onload = func;
	else
	{
		var old_onload = window.onload;
		window.onload = function()
		{
		  old_onload();
		  func();
		}
	}
}


/**
 * Helper function to include external JS files on the fly.
 *
 * @param string file the absolute or relative path to the javascript file
 * @param string file_id a unique ID for the file
 */
g.importJSFile = function(file, file_id)
{
  // if this file is already included, just return
	if ($(file_id))
	  return;

	// add the file by appending it to the end of the <head>
	var head = document.getElementsByTagName("head")[0];
	script = document.createElement('script');
	script.id = file_id;
	script.type = 'text/javascript';
	script.src = file + "?id=" + g.version;
	head.appendChild(script);
}


/**
 * Helper function to include external JS files on the fly.
 *
 * @param string file the absolute or relative path to the javascript file
 * @param string file_id a unique ID for the file
 */
g.importCSSFile = function(file, file_id)
{
  // if this file is already included, just return
	if ($(file_id))
	  return;

	// add the file by appending it to the end of the <head>
	var head = document.getElementsByTagName("head")[0];
	var cssLink = document.createElement('link');
	cssLink.rel = "stylesheet";
	cssLink.id = file_id;
	cssLink.type = 'text/css';
	cssLink.href = file + "?id=" + g.version
	head.appendChild(cssLink);
}


/**
 * The default error handler for the rsv validation library.
 */
function g_rsvErrors(f, errorInfo)
{
  var errorHTML = rsv.errorTextIntro + "<br /><br />";
  for (var i=0; i<errorInfo.length; i++)
  {
    errorHTML += rsv.errorHTMLItemBullet + errorInfo[i][1] + "<br />";

    if (rsv.styleOffendingFields)
      rsv.styleField(errorInfo[i][0], i==0);
  }

  if (errorInfo.length > 0)
  {
    g.displayMessage(rsv.errorTargetElementId, 0, errorHTML);
	  return false;
	}

	return true;
}


/**
 * Generic function for displaying a message in the UI, as returned from an Ajax response handler. This
 * does all the fancy-pants stuff like blind-downing the message, and changing the colour on the message
 * with the Fade Anything Technique (fat.js) to draw attention to it.
 *
 * Assumption: the element with the target_id contains a single DIV tag. This is done so that the styles
 * may be applied to the inner DIV, allowing the outer div to be smoothly blind-upped and -downed.
 *
 * @param string target_id the HTML target element
 * @param boolean success whether this is an error or a notification
 * @param string message the message to display
 */
g.displayMessage = function(target_id, success, message)
{
  var messageClass = (success == 1) ? "notify" : "error";

  // remove all old class names
  $(target_id).classNames().each(function(s) { $(target_id).removeClassName(s); });

  // add the new one
  $(target_id).addClassName(messageClass);
  $(target_id).innerHTML = "<div style=\"padding:8px\">"
    + "<a href=\"#\" onclick=\"return g.hideMessage('" + target_id + "')\" style=\"float:right\">X</a>"
    + message + "</div>";
  $(target_id).style.display = "block";

	// add the nice fade effect for the notification message
	Fat.fade_element(target_id, 60, 1500, '#FFFC05', '#ffffdd');
}


/**
 * Hides a message on the screen by fading it out and blinding up at the same time.
 */
g.hideMessage = function(target_id)
{
  Effect.BlindUp($(target_id));
  Effect.Fade($(target_id));

  return false;
}


/**
 * Simple helper function that's called on all server-side requests; it checks that the
 * person is currently logged in. If they're not, it sends them to the logout page.
 */
g.checkLoggedIn = function(is_logged_in)
{
  if (!is_logged_in)
    top.location = g.logoutURL + "?session_timeout=1";
}

// DEPRECATE THIS for prototype's include()
Array.prototype.contains = function(value)
{
  var found = false;

  for (var i=0; i<this.length; i++)
  {
    if (this[i] == value)
      found = true;
  }

  return found;
}

String.prototype.ucFirstLetter = function() { return this.substr(0,1).toUpperCase() + this.substr(1); }
String.prototype.lcFirstLetter = function() { return this.substr(0,1).toLowerCase() + this.substr(1); }

/**
 * Simple function that acts like String.join(), except it allows for a custom final joiner. This
 * is helpful in situations where you want to join an array in human readable form, e.g.
 *   var arr = ["one", "two", "three"];
 *   alert(arr.customJoin(", ", " and ")); // alerts "one, two and three"
 */
Array.prototype.customJoin = function(glue, finalGlue)
{
  // if there's at least one element, pop off the LAST item
  var lastItem = null;
  if (this.length > 0)
    lastItem = this.pop();

  // now, if there's still 1 or more items left, join them with the glue
  if (this.length > 0)
  {
    var str = this.join(glue);
    str += finalGlue + lastItem;
  }
  // otherwise there was only one item: just return the string
  else
    str = lastItem;

  return str;
}

/**
 * Helper function to uncheck a list of elements, specified by an array of IDs.
 */
g.uncheck = function(id_arr)
{
  for (var i=0; i<id_arr.length; i++)
    $(id_arr[i]).checked = false;
}

/**
 * Checks a list of elements, specified by an array of IDs.
 */
g.check = function(id_arr)
{
  for (var i=0; i<id_arr.length; i++)
    $(id_arr[i]).checked = true;
}

g.setNullValue = function(id_arr)
{
  for (var i=0; i<id_arr.length; i++)
    $(id_arr[i]).value = "";
}

g.setNullInnerHTML = function(id_arr)
{
  for (var i=0; i<id_arr.length; i++)
    $(id_arr[i]).innerHTML = "";
}

g.oneIsChecked = function(id_arr)
{
  for (var i=0; i<id_arr.length; i++)
  {
    if ($(id_arr[i]).checked)
      return true;
  }

  return false;
}


g.applyRadioCellEvents = function(update_functions)
{
	$$(".radio_cell").each(
		function(el)
		{
      // find the radio button element (Assumes there's ONLY one child node)
      var radio_el = $(el).childNodes[0];
      $(el).observe('mouseover', function() { g.radioCellMouseover(el, radio_el); });
      $(el).observe('mouseout', function() { g.radioCellMouseout(el, radio_el); });
      $(el).observe('click', function() { g.radioCellClick(el, radio_el, update_functions); });
		}
	);
}

g.radioCellMouseover = function(td_el, radio_el)
{
  var checked_val = $F(radio_el);
  if (checked_val != radio_el.value)
    $(td_el).addClassName("radio_cell_over");
}

g.radioCellMouseout = function(td_el, radio_el)
{
  var checked_val = $F(radio_el);
  if (checked_val != radio_el.value)
    $(td_el).removeClassName("radio_cell_over");
}

g.radioCellClick = function(td_el, radio_el, update_functions)
{
  for (var i=0; i<radio_el.form[radio_el.name].length; i++)
  {
    if (radio_el.value == radio_el.form[radio_el.name][i].value)
    {
	    radio_el.checked = true;
	    if ($(td_el).hasClassName("radio_cell_over"))
	      $(td_el).removeClassName("radio_cell_over");

	    $(td_el).addClassName("radio_cell_checked");
    }
    else
    {
      var ancestors = $(radio_el.form[radio_el.name][i]).ancestors();

      if ($(ancestors[0]).hasClassName("radio_cell_checked"))
        $(ancestors[0]).removeClassName("radio_cell_checked");
    }
  }

  // run whatever update functions are required
  for (var i=0; i<update_functions.length; i++)
    update_functions[i]();
}


g.addFieldEventHandler = function(className, func)
{
  // loop through all elements in the page with that class and assign the appropriate event handler
  // based on the field type
  var fields = $$(className);
  for (var i=0; i<fields.length; i++)
  {
    switch (fields[i].type)
    {
      case "radio":
      case "checkbox":
        $(fields[i]).observe('click', function(e) { func() });
        break;
      case "select":
      case "select-one":
        $(fields[i]).observe('change', function(e) { func() });
        break;

      default:
        $(fields[i]).observe('change', function(e) { func() }); // for IE
        $(fields[i]).observe('keyup', function(e) { func() });
        break;
    }
  }
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/*
  [0] : code to execute - (function)
	[1] : test to determine completion - (function, returning boolean)
	[2] : interval ID (managed internally by script) - (integer)
*/
g.queue = [];

/**
 * A generic queuing function for javascript tasks. Handy for ensuring sequential execution of
 * code based on whatever criteria you need. To use, just push() something onto g.queue and run
 * this function
 */
g.processQueue = function()
{
  if (!g.queue.length)
    return;

  // if this code hasn't begun being executed, start 'er up
  if (!g.queue[0][2])
  {
    var timeout_id = window.setInterval("g.checkQueueItemComplete()", 50);
    g.queue[0][2] = timeout_id;
  }
}

g.checkQueueItemComplete = function()
{
  // here, the conditions have been met! So we can execute the code and remove this from the queue
  if (g.queue[0][1]())
  {
    closure_func = g.queue[0][0];
    setTimeout("closure_func()", 500);

    window.clearInterval(g.queue[0][2]);
    g.queue.shift();
    g.processQueue();
  }
}
