/**
 * Set of static methods to allow Document Object Model (DOM)
 * nodes manipulation
 */
var TDOM = function() { }

/**
 * Get an element in document
 *
 * @param {Mixed} el: DOM Element or element id
 * @retun {Object} element or null if not found
 */
TDOM.getElement = function(el) {
  return typeof el == "string" ? document.getElementById(el) : el;
}

/**
 * Return the document node of an element
 * If no el is sent, current document is returned
 *
 * @param {Mixed} el: DOM Element or element id
 * @return {Document} document element
 */
TDOM.getDocumentNode = function(el) {
  el = TDOM.getElement(el) || document;
  return el.nodeType == 9 ? el : el.ownerDocument || el.document || document;
}

/**
 * Return the root node of the document of an element
 * @return {Body} document root node
 */
TDOM.getDocumentElement = function(el) {
  var doc = TDOM.getDocumentNode(el);
  return document.compatMode == 'CSS1Compat' ? doc.documentElement : doc.body;
}

/**
 * Resolve an HTML Text Node to his HTML Element parent
 *
 * @param {Node} node: DOM Node
 * @retun {Element} DOM Element Node
 */
TDOM.resolveTextNode = function(node) {
  return node && node.nodeType == 3 && node.parentNode ? node.parentNode : node;
}

/**
 * Seek an HTML Element node starting from node
 *
 * @param {Node} node: node from where start seeking
 * @return {Boolean} prev: true to look backward, false to look forward
 */
TDOM.seekElement = function(node, prev) {
  while (node && node.nodeType != 1)  {
    node = prev ? node.previousSibling : node.nextSibling;
  }
  return node;
}

/**
 * Return the next node of type Element
 *
 * @param {Node} node: node from where start seeking
 * @return {Element} node of type 1 or null if none found
 */
TDOM.nextElement = function(node) {
  return TDOM.seekElement(node ? node.nextSibling : null);
}

/**
 * Return the previous node of type Element
 *
 * @param {Node} node: node from where start seeking
 * @return {Element} node of type 1 or null if none found
 */
TDOM.previousElement = function(node) {
  return TDOM.seekElement(node ? node.previousSibling : null, true);
}

/**
 * Return the first child Element node
 *
 * @param {Node} node: node from where start seeking
 * @return {Element} node of type 1 or null if none found
 */
TDOM.firstChild = function(node) {
  return TDOM.seekElement(node ? node.firstChild : null);
}

/**
 * Return the last child Element node
 *
 * @param {Node} node: node from where start seeking
 * @return {Element} node of type 1 or null if none found
 */
TDOM.lastChild = function(node) {
  return TDOM.seekElement(node ? node.lastChild : null);
}

/**
 * Insert an element node before or after
 * another element node
 *
 * @param {Element} a: element node to insert
 * @param {Boolean} after: true intert a after b, false insert a before b
 * @param {Element} b: node of reference
 */
TDOM.insertElement = function(a, after, b) {
  if (b && b.parentNode) {
    c = after && after != 'before' ? TDOM.nextElement(b) : b;
    if (c) {
      b.parentNode.insertBefore(a,c);
    } else {
      b.parentNode.appendChild(a);
    }
  }
}

/**
 * Remove an element from the DOM tree
 * @param {Element} el: element to remove
 */
TDOM.removeElement = function(el) {
  if (el && el.parentNode) {
    el.parentNode.removeChild(el);
  }
}

/**
 * Test if an object is visible in document
 * @note: Test display property only.
 *        Hidden elements using visibility may return true
 */
TDOM.isVisible = function(el) {
  return !TDOM.getHiddenParent(el);
}

/**
 * Return the first hidden (display = none) parent element
 * of the supplied element, if any
 */
TDOM.getHiddenParent = function(el) {
  var parent = el;
  while (parent && parent.nodeName && parent.nodeName != 'BODY') {
    if (TDOM.getStyle(parent, "display") == "none") {
      return parent;
    }
    parent = parent.parentNode;
  }
  return false;
}

/**
 * Force an element to get visible by walking through
 * each parent and making sure they are all display = block
 */
TDOM._hiddenStack = [];
TDOM.makeVisible = function(el,visible) {
  var parent = TDOM.getHiddenParent(el);
  while (parent) {
    TDOM._hiddenStack.push([parent, parent.style.display, parent.style.visibility]);
    parent.style.visibility = visible ? '' : "hidden";
    parent.style.display = "block";
    parent = TDOM.getHiddenParent(el);
  }
}

/**
 * Restore visibility of elements displayed with makeVisible()
 * to their previous state
 */
TDOM.restoreVisible = function() {
  var parent = TDOM._hiddenStack.pop();
  while (parent) {
    parent[0].style.display = parent[1];
    parent[0].style.visibility = parent[2];
    parent = TDOM._hiddenStack.pop();
  }
  TDOM._hiddenStack = [];
}

/**
 * Return the size of an element
 *
 * @param {Mixed} el: DOM Element or element id
 * @return {Object} element size object
 */
TDOM.getSize = function(el, autoWidth) {
  var w = h = 0;
  el = TDOM.getElement(el);
  TDOM.makeVisible(el);
  w = el.offsetWidth ? el.offsetWidth : 0;
  h = el.offsetHeight ? el.offsetHeight : 0;
  TDOM.restoreVisible(el);
  return {width:w, height:h};
}

/**
 * Return the position of an element in document
 *
 * @param {Mixed} el: DOM Element or element id
 * @return {Point} point object
 */
TDOM.getPos = function(el) {
  el = TDOM.getElement(el);
  var doc = TDOM.getDocumentNode(el);
  if (el.getBoundingClientRect) {
    var box = el.getBoundingClientRect();
    var scroll = TDOM.getScroll(doc);
    return {x:box.left + scroll.x, y:box.top  + scroll.y}
  } else {
    var y = el.offsetTop;
    var x = el.offsetLeft;
    var parent = el.offsetParent;
    if (parent != el) {
      while (parent) {
        if (TUserAgent.IE && TDOM.getStyle(parent, 'position') == 'relative') {
          break;
        }
        y += parent.offsetTop;
        x += parent.offsetLeft;
        parent = parent.offsetParent;
      }
    }
    if (TUserAgent.OPERA || TUserAgent.SAFARI && TDOM.getStyle(el, "position") == "absolute") {
      y -= doc.body.offsetTop;
    }
    parent = el.parentNode;
    var body = TDOM.getDocumentElement(doc);
    while (parent && parent != body) {
      x -= parent.scrollLeft;
      if (!TUserAgent.OPERA || parent.tagName != "TR") {
        y -= parent.scrollTop;
      }
      parent = parent.parentNode;
    }
    return {x:x, y:y};
  }
}

/**
 * Return a rect object with the bounds of the given element
 *
 * @param {Mixed} el: DOM Element or element id
 * @return {Rect} bounds of the element
 */
TDOM.getBounds = function(el, autoWidth) {
  var size = TDOM.getSize(el, autoWidth);
  var pos = TDOM.getPos(el);
	return {top:pos.y, left:pos.x, width:size.width, height:size.height};
}

/**
 * Return the size of the viewport
 * (Current visible area of the window)
 * @return {Object} viewport size object
 */
TDOM.getViewportSize = function() {
  // Safari
  var w = window.innerWidth;
  var h = window.innerHeight;
  if (!w && !h) {
    // Strict or quirks mode
    var b = TDOM.getDocumentElement();
    var w = b ? b.clientWidth : 0;
    var h = b ? b.clientHeight : 0;
  }
  return {width:w, height:h};
}

/**
 * Return the size of the document
 * (Entire document size, including scrolls)
 * @return {Object} document size object
 */
TDOM.getDocumentSize = function() {
  var v = TDOM.getViewportSize();
  var b = TDOM.getDocumentElement();
  return {width:Math.max(b.scrollWidth, v.width), height:Math.max(b.scrollHeight, v.height)};
}

/**
 * Return a point object with the document scrollbars positions
 * @return {Point} document scroll positions
 */
TDOM.getScroll = function(doc) {
  var b = TDOM.getDocumentElement(doc);
  return {x:b.scrollLeft, y:b.scrollTop};
}

/**
 * Return a Rect object with the bounds of the viewport
 * @return {Rect} viewpor bounds
 */
TDOM.getViewportBounds = function() {
  var pos = TDOM.getScroll();
  var size = TDOM.getViewportSize();
  return {top:pos.y, left:pos.x, width:size.width, height:size.height};
}

/**
 * Test is an element has a class
 *
 * @param {Element} el
 * @param {String} cl: class name to test
 */
TDOM.hasClass = function(el,cl) {
  el = TDOM.getElement(el);
  if (el) {
    var re = new RegExp('(^|\\s+)?' + cl + '(\\s+|$)?');
    return re.test(el.className);
  }
}

/**
 * Add a new class to an element
 *
 * @param {Element} el
 * @param {String} cl: class to add
 */
TDOM.addClass = function(el,cl) {
  el = TDOM.getElement(el);
  if (el) {
    if (TDOM.hasClass(el,cl)) return;
    if (!el.className) {
      el.className = cl;
    } else {
      el.className = [el.className, cl].join(' ');
    }
  }
}

/**
 * Remove a class from an element
 *
 * @param {Object} el
 * @param {String} cl: class to remove
 */
TDOM.removeClass = function(el,cl) {
  el = TDOM.getElement(el);
  if (!TDOM.hasClass(el,cl)) return;
  var re = new RegExp('(^|\\s+)?' + cl + '(\\s+|$)?','g');
  var cl = el.className;
  el.className = cl.replace(re,' ');
}

/**
 * Get an style attribute value of an element
 *
 * @param {Element} el: DOM Element
 * @param {String} property: css property
 */
TDOM.getStyle = function(el, property) {
  el = TDOM.getElement(el);
  var doc = TDOM.getDocumentNode(el);
  // W3C way
  if (doc.defaultView && doc.defaultView.getComputedStyle) {
    property = property == 'float' ? 'cssFloat' : property;
    var styles = doc.defaultView.getComputedStyle(el, "");
    if (styles) {
      if (property == 'opacity') {
        property = "MozOpacity" in styles ? "MozOpacity" : property;
        property = "KhtmlOpacity" in styles ? "KhtmlOpacity" : property;
      }
      return styles[property];
    }
  } else
  // IE way
  if (el.currentStyle) {
    if (property == 'opacity' && el.filters) {
      var val = 100;
      try {
        val = el.filters['DXImageTransform.Microsoft.Alpha'].opacity;
      } catch(e) {
        try {
          val = el.filters('alpha').opacity;
        } catch(e) { }
      }
      return val / 100;
    }
    property = property == 'float' ? 'styleFloat' : property;
    return el.currentStyle[property];
  }
  return el.style[property];
}

/**
 * Set an style to an element
 *
 * @param {Element} el: DOM element
 * @param {String} property: css property
 */
TDOM.setStyle = function(el, property, value) {
  el = TDOM.getElement(el);
  if (property == 'opacity') {
    if (value < 0 || value > 1) {
      return;
    }
    if ("opacity" in el.style) {
      el.style.opacity = value;
    } else
    if ("MozOpacity" in el.style) {
      el.style.MozOpacity = value;
    } else
    if ("KhtmlOpacity" in el.style) {
      el.style.KhtmlOpacity = value;
    } else
    if ("filter" in el.style) {
      el.style.filter = "alpha(opacity=" + value * 100 + ")";
    }
    return;
  }
  if (property == 'float') {
    property = TUserAgent.IE ? 'styleFloat' : 'cssFloat';
  }
  el.style[property] = value;
}

