// Copyright 2005-2006 Google Inc. All Rights Reserved.
/**
 * @fileoverview This file contains miscellaneous basic functionality.
 *
 */

/**
 * Creates a DOM element with the given tag name in the document of the
 * owner element.
 *
 * @param {String} tagName  The name of the tag to create.
 * @param {Element} owner The intended owner (i.e., parent element) of
 * the created element.
 * @param {Point} opt_position  The top-left corner of the created element.
 * @param {Size} opt_size  The size of the created element.
 * @param {Boolean} opt_noAppend Do not append the new element to the owner.
 * @return {Element}  The newly created element node.
 */
function createElement(tagName, owner, opt_position, opt_size, opt_noAppend) {
  var element = ownerDocument(owner).createElement(tagName);
  // NOTE: Firefox/1.5 makes elements visible as soon as they
  // are inserted in the DOM. Hence, position must be set before
  // appending the element to its parent in order to avoid it showing
  // up transiently at the origin before placed at the right
  // position. NOTE: I don't think that this triggers the
  // reverse DOM insertion memory leak in IE.
  if (opt_position) {
    setPosition(element, opt_position);
  }
  if (opt_size) {
    setSize(element, opt_size);
  }
  if (owner && !opt_noAppend) {
    appendChild(owner, element);
  }

  return element;
}

/**
 * Creates a text node with the given value.
 *
 * @param {String} value  The text to place in the new node.
 * @param {Element} owner The owner (i.e., parent element) of the new
 * text node.
 * @return {Text}  The newly created text node.
 */
function createTextNode(value, owner) {
  var element = ownerDocument(owner).createTextNode(value);
  if (owner) {
    appendChild(owner, element);
  }
  return element;
}

/**
 * Returns the document owner of the given element. In particular,
 * returns window.document if node is null or the browser does not
 * support ownerDocument.
 *
 * @param {Node} node  The node whose ownerDocument is required.
 * @returns {Document|Null}  The owner document or null if unsupported.
 */
function ownerDocument(node) {
  return (node ? node.ownerDocument : null) || document;
}

/**
 * Wrapper function to create CSS units (pixels) string
 *
 * @param {Number} numPixels  Number of pixels, may be floating point.
 * @returns {String}  Corresponding CSS units string.
 */
function px(numPixels) {
  return round(numPixels) + "px";
}

/**
 * Sets the left and top of the given element to the given point.
 *
 * @param {Element} element  The dom element to manipulate.
 * @param {Point} point  The desired position.
 */
function setPosition(element, point) {
  var style = element.style;
  style.position = "absolute";
  style.left = px(point.x);
  style.top = px(point.y);
}

/**
 * Sets the width and height style attributes to the given size.
 *
 * @param {Element} element  The dom element to manipulate.
 * @param {Size} size  The desired size.
 */
function setSize(element, size) {
  var style = element.style;
  style.width = px(size.width);
  style.height = px(size.height);
}

/**
 * Sets display to none. Doing this as a function saves a few bytes for
 * the 'style.display' property and the 'none' literal.
 *
 * @param {Element} node  The dom element to manipulate.
 */
function displayNone(node) {
  node.style.display = 'none';
}

/**
 * Sets display to default.
 *
 * @param {Element} node  The dom element to manipulate.
 */
function displayDefault(node) {
  node.style.display = '';
}

/**
 * Appends the given child to the given parent in the DOM
 *
 * @param {Element} parent  The parent dom element.
 * @param {Node} child  The new child dom node.
 */
function appendChild(parent, child) {
  parent.appendChild(child);
}


/**
 * Wrapper for the eval() builtin function to evaluate expressions and
 * obtain their value. It wraps the expression in parentheses such
 * that object literals are really evaluated to objects. Without the
 * wrapping, they are evaluated as block, and create syntax
 * errors. Also protects against other syntax errors in the eval()ed
 * code and returns null if the eval throws an exception.
 *
 * @param {String} expr
 * @return {Object|Null}
 */
function jsEval(expr) {
  try {
    // NOTE: An alternative idiom would be:
    //
    //   eval('(' + expr + ')');
    //
    // Note that using the square brackets as below, "" evals to undefined.
    // The alternative of using parentheses does not work when evaluating
    // function literals in IE.
    // e.g. eval("(function() {})") returns undefined, and not a function
    // object, in IE.
    return eval('[' + expr + '][0]');
  } catch (e) {
    return null;
  }
}


/**
 * Wrapper for the eval() builtin function to execute statements. This
 * guards against exceptions thrown, but doesn't return a
 * value. Still, mostly for testability, it returns a boolean to
 * indicate whether execution was successful. NOTE:
 * javascript's eval semantics is murky in that it confounds
 * expression evaluation and statement execution into a single
 * construct. Cf. jsEval().
 *
 * @param {String} stmt
 * @return {Boolean}
 */
function jsExec(stmt) {
  try {
    eval(stmt);
    return true;
  } catch (e) {
    return false;
  }
}


/**
 * Wrapper for eval with a context. NOTE: The style guide
 * deprecates eval, so this is the exception that proves the
 * rule. Notice also that since the value of the expression is
 * returned rather than assigned to a local variable, one major
 * objection aganist the use of the with() statement, namely that
 * properties of the with() target override local variables of the
 * same name, is void here.
 *
 * @param {String} expr
 * @param {Object} context
 * @return {Object|Null}
 */
function jsEvalWith(expr, context) {
  try {
    with (context) {
      // See comment in jsEval.
      return eval('[' + expr + '][0]');
    }
  } catch (e) {
    return null;
  }
}