// TODO: Refactor and determine proper place in the namespace hierarchy.
/**
 * RunTrackr Utility namespace.
 *
 * Contains utility functions for other JavaScript code in RunTrackr.
 *
 * Requires the jQuery library.
 */

if (typeof(runtrackr) == 'undefined')
{
  runtrackr = {};
}
runtrackr.Utility = new Object();

// TODO: Write a utility function that can automatically convert a JSON
// object to the data[][] format needed by CakePHP.  Example:
// Takes data = {'Country':{'name':'value'}} and converts to
// postData = {'data[Country][name]':'value'}
// Is this needed?

// TODO: Needs extensive unit testing, but appears to be working...
/**
 * Searches recursively in an object for the first property with the specified
 * name and then returns its value.  Thus, if the object contains other
 * objects, those are also searched for the property name.
 *
 * The return type is not specified.  If the property name was not found, this
 * function is guaranteed to return 'false', but beware that it might return
 * 'false' if that was the value associated with the found property name.
 * Thus, this function SHOULD NOT be used where the values to be searched for
 * may be booleans.
 *
 * Since only the first found property is returned, this function is not ideal
 * for searching through objects whose inner object may have identical
 * property names.
 *
 * @param Object object the object to search for the specified property name.
 * @param string propertyName the property name whose value should be returned.
 *        If there are multiple identical property names, only the first one
 *        is returned.
 * @return the variable/value associated with the property name.  The type
 *         cannot be specified any could be anything. Guaranteed to return
 *         'false' if the property name was not found, but may return 'false'
 *         if that was the found property value.
 * @static
 */
runtrackr.Utility.getPropertyValue = function(object, propertyName)
{
  // If the supplied object is undefined, null or not an object, return false.
  if (!object || typeof(object) != "object")
  {
    return false;
  }

  // Look at all properties in the object.
  for (var key in object)
  {
    // Found the right property; return immediately with its value.
    if (key == propertyName)
    {
      return object[key];
    }
    // See if the property value is an object; if so, search it recursively.
    else if (object && typeof(object[key]) == 'object')
    {
      // If the property value was found in the child object, return its value
      // immediately.  Otherwise, continuing looking at all the other properties
      // in the present object by making a recursive function call.
      var foundInChild = arguments.callee(object[key], propertyName);
      if (foundInChild !== false)
      {
        return foundInChild;
      }
    }
  }
  // Not found.
  return false;
}

/**
 * Returns the specified property from an object if it exists, otherwise returns
 * the specified default value.
 *
 * @param Object object the object to get the property from.
 * @param String propertyName the property to retrieve.
 * @param String defaultValue the value to return if the property was not found.
 * @return Object the value associated with the property name.
 * @static
 */
runtrackr.Utility.getPropertyValueDefault = function(object, propertyName, defaultValue)
{
//  return (object.propertyName) ? object.propertyName : defaultValue;
  if (propertyName in object)
    return object.propertyName;
  else
    return defaultValue;
}



/**
 * Trims leading and trailing whitespace from a string.
 *
 * Source: http://www.somacon.com/p355.php
 *
 * @param String stringToTrim the string to trim leading/trailing whitespace
 *        from
 * @return String the leading and trailing whitespace-trimmed string.
 * @static
 * @deprecated use jQuery.trim(str) instead.
 */
runtrackr.Utility.trim = function(stringToTrim)
{
  return stringToTrim.replace(/^\s+|\s+$/g,"");
}

/**
 * Gets the URI fragment/hash after URI decoding.
 * TODO: How will trying to separate multiple parameters by delimiters be
 * affected by decoding the entire fragment?
 */
runtrackr.Utility.getUriFragment = function()
{
  // Extract the fragment without the `#` symbol/prefix..
  return decodeURIComponent(window.location.hash.substr(1));
}

/**
 * Moves an element's position so that it is within another one.
 *
 * The first element should be absolutely positioned and should have as its
 * containing block the root element.
 * http://reference.sitepoint.com/css/containingblock
 *
 * @param jQuery elementToPlaceSelector the jQuery selector to the element to place.
 * @param jQuery containerElementSelector the jQuery selector to the container element.
 * @param Object offset the offset.top/offset.left in pixels to position the element
 *        relative to the 'container.  If not present, the element's current
 *        offset is used. (optional)
 */
runtrackr.Utility.movePosition = function(elementToPlaceSelector, containerElementSelector, offset)
{
  var containerOffset = jQuery(containerElementSelector).offset();
  var elementToPlace = jQuery(elementToPlaceSelector);

  if (!offset)
  {
    offset = {
      top: parseInt(elementToPlace.css('top')),
      left: parseInt(elementToPlace.css('left'))
    };
  }

  elementToPlace.css({
    top: containerOffset.top + offset.top,
    left: containerOffset.left + offset.left
  })
}

/**
 * Places a greyed-out overlay on top of the specified element, preventing
 * interaction with it.
 *
 * The overlay will have a class of 'overlay-disabled' for easy removal, which
 * can be accomplished by calling runtrackr.Utility.removeAllDisablingOverlays().
 *
 * @param jQuery element the DOM element to add the disabled overlay on top of.
 * @static
 */
runtrackr.Utility.addDisablingOverlay = function(element)
{
  var offset = element.offset();
  var width = element.width();
  var height = element.height();

  var overlay = jQuery(document.createElement('div'));
  overlay.addClass('overlay-disabled').
  css({
    width: width,
    height: height,
    top: offset.top,
    left: offset.left
  });

  // Attach to the body element so absolute positioning offsets are guaranteed
  // to work.
  jQuery('body').append(overlay);
}

/**
 * Removes all disabling overlays that were added by
 * runtrackr.Utility.addDisabledOverlay().
 *
 * @static
 */
runtrackr.Utility.removeAllDisablingOverlays = function()
{
  jQuery('.overlay-disabled').remove();
}

/**
 * Links a field with its corresponding tooltip.
 *
 * The class of the tooltip element should the same as the id of the field
 * supplied and should additionally have a class of `tooltip`.  The tooltip
 * will be displayed near the field when it receives focus and will
 * disappear when focus is lost. (blur event)
 *
 * @static
 */
runtrackr.Utility.addTooltip = function(elementId)
{
  // Show the tooltip when the element receives focus.
  jQuery(elementId).focus(runtrackr.Utility.addTooltip._focusHandler);

  // Remove the tooltip when the field loses focus. (Blur)
  jQuery(elementId).blur(runtrackr.Utility.addTooltip._blurHandler);
}

/**
 * Tooltip focus event handler.
 *
 * @private
 * @static
 */
runtrackr.Utility.addTooltip._focusHandler = function(event)
{
  // TODO: allow the offset to be supplied, so this can be reused without
  // re-writing.  Also allow the height/width of the tooltip to be specified
  // as well, beyond the default.
  // TODO: Add in a nice divet/arrow so the tooltip points to the field...
  // this background image should be able to be positioned as well...

  var fieldId = this.id;

  var thisInput = jQuery(this);

  var offset = thisInput.offset();
  var height = thisInput.height();
  var width = thisInput.width();
  var padding = parseInt(thisInput.css('padding-top')) + parseInt(thisInput.css('padding-bottom'));
  var border = parseInt(thisInput.css('border-top-width')) + parseInt(thisInput.css('border-bottom-width'));
  var margin = parseInt(thisInput.css('margin-top')) + parseInt(thisInput.css('margin-bottom'));

  // The tooltip should have the same class name as the id of the field, as
  // well as the class name `tooltip`.
  var tooltip = jQuery('.tooltip.' + fieldId);
  tooltip.css({
    top : offset.top + height + padding + border + margin + 5,
    left: offset.left
  });
  tooltip.show();
}

/**
 * Tooltip blur event handler.
 *
 * @private
 * @static
 */
runtrackr.Utility.addTooltip._blurHandler = function(event)
{
  var fieldId = this.id;
  var tooltip = jQuery('.tooltip.' + fieldId);
  tooltip.hide();
}

/**
 * Calculates and returns the approximate number of calories burnt, using an
 * estimate found here:
 * http://answers.google.com/answers/threadview?id=758572
 *
 * @param Number weight the weight of the person in kg.
 * @param Number distance the distance they ran in km.
 * @return Number the approx. number of calories burnt. (kcal)
 * @static
 */
runtrackr.Utility.calcCalories = function(weight, distance)
{
  // Calories approximation: Weight in kilograms multiplied by distance in
  // kilometers.
  var calories = weight * distance;
  return calories;
}

/**
 * Hides the list of location matches.
 */
runtrackr.Utility.hideLocationMatches = function(e)
{
  e.preventDefault();
  jQuery('.placemarks-list').hide();
}

/**
 * Shows a list of possible location matches when a user's search resulted
 * in multiple matches from the geocoder.
 *
 * @param Array places the array of Placemark objects from the geocoder.
 * @param Function setLocationCallback the callback to invoke when one of the
 *        matches is clicked by the user.  The Placemark JSON object
 *        corresponding to the location clicked is passed in.
 * @static
 */
runtrackr.Utility.showLocationMatches = function(places, setLocationCallback)
{
  var placemarksListPath = '.placemarks-list';

  // Form the list of potential matches.
  var placemarksList = '';
  for (var i in places)
  {
    var place = places[i];

    placemarksList += '<li class="placemark-option">' +
      '<input type="hidden" value="' + i + '" />' +
      '<a href="#" class="address">' + place['address'] + '</a></li>';
  }

  // Attach the list beneath the search field, removing any previous results.
  jQuery(placemarksListPath + ' ul').empty().append(placemarksList);
  jQuery(placemarksListPath).css({width: jQuery('#location-input').width()}).show();
  runtrackr.Utility.movePosition(placemarksListPath, '#location-input', {top: 31, left: 0});

  // TODO: Are these possible without creating a closure?
  // Event handler to invoke setLocationCallback when a location is clicked.
  jQuery(placemarksListPath + ' ul a').click(function(e)
  {
    e.preventDefault();
    var index = jQuery(this).parents('li').find('input').val();
    setLocationCallback(places[index]);
    jQuery(placemarksListPath).hide();
  });

  // Event handler to focus the search field when `search again` clicked.
  jQuery(placemarksListPath + ' a.search-again').click(function(e)
  {
    e.preventDefault();
    jQuery(placemarksListPath).hide();
    jQuery('#location-input').focus();
  });

  // Event handler to close the suggestion list when `close` clicked.
  jQuery(placemarksListPath + ' .cancel').click(runtrackr.Utility.hideLocationMatches);
}
