/**
 * @fileOverview Métodos necesarios para acceder al DOM.
 */

/*** Namespace *****************************************************************************************************/
dome.namespace("dome");

/**
 * @class Clase que proporciona los métodos necesarios para acceder al DOM.
 * @static
 *
 * @requires dome
 *
 * @see http://www.w3.org/DOM/DOMTR#dom2
 * @version 0.1
 */
dome.Dom = {};

/**
 * @memberOf dome.Dom
 * @constant
 * @type int
 * @private
 */
dome.Dom.version = 1;

/**#@+
 * @memberOf dome.Dom
 * @constant
 * @type int
 */

/** Tipo ELEMENT_NODE*/
dome.Dom.ELEMENT_NODE = 1;

/** Tipo ATTRIBUTE_NODE */
dome.Dom.ATTRIBUTE_NODE = 2;

/** Tipo TEXT_NODE */
dome.Dom.TEXT_NODE = 3;

/** Tipo CDATA_SECTION_NODE */
dome.Dom.CDATA_SECTION_NODE = 4;

/** Tipo ENTITY_REFERENCE_NODE */
dome.Dom.ENTITY_REFERENCE_NODE = 5;

/** Tipo ENTITY_NODE */
dome.Dom.ENTITY_NODE = 6;

/** Tipo PROCESSING_INSTRUCTION_NODE */
dome.Dom.PROCESSING_INSTRUCTION_NODE = 7;

/** Tipo COMMENT_NODE */
dome.Dom.COMMENT_NODE = 8;

/** Tipo DOCUMENT_NODE */
dome.Dom.DOCUMENT_NODE = 9;

/** Tipo DOCUMENT_TYPE_NODE */
dome.Dom.DOCUMENT_TYPE_NODE = 10;

/** Tipo DOCUMENT_FRAGMENT_NODE */
dome.Dom.DOCUMENT_FRAGMENT_NODE = 11;

/** Tipo NOTATION_NODE */
dome.Dom.NOTATION_NODE = 12;

/**#@-*/

/**
 * Añade el nodo <code>newChild</code> al final de la lista de hijos del nodo indicado en <code>node</code>. Si el
 * nodo <code>newChild</code> ya existe, entonces primero se elimina.
 *
 * @param {Node} node      El nodo al que se quiere añadir el elemento.
 * @param {Node} newChild  El nodo que se quiere añadir.
 */
dome.Dom.appendChild = function (node, newChild) {
   if (node != null && newChild != null) {
      node.appendChild(newChild);
   }
};

/**
 * Recupera el atributo con el nombre especificado. Si el atributo no existe o alguno de los parámetros es
 * <code>null</code>, entonces se devolverá <code>null</code>.
 *
 * @param node {Node}   El nodo del que se quiere recupera el atributo.
 * @param name {String} Nombre del atributo.
 *
 * @return  {Attr} El atributo del nodo con el nombre especificado o <code>null</code> si no existe.
 */
dome.Dom.getAttribute = function (node, name) {
   if (node == null || name == null) { return null; }

   return node.getAttributeNode(name);
};

/**
 * Devuelve un <code>Array</code> con los atributos del nodo indicado. Si el nodo no tiene atributos entonces
 * se retorna un <code>Array</code> vacío. En caso de que el nodo especificado sea <code>null</code> se retorna
 * <code>null</code>.
 *
 * @param node {Node}   El nodo del que se que quiere recuperar los atributos.
 *
 * @return {Array}   Atributos del nodo indicado.
 */
dome.Dom.getAttributes = function (node) {
   if (node == null) { return null; }

   var attrMap = node.attributes;

   if (attrMap != null) {
      var attr = new Array();
      for (var i = 0; i < attrMap.length; i++) {
         attr[i] = attrMap.item(i);
      }
      return attr;
   } else {
      return new Array();
   }
};

/**
 * Devuelve el valor del atributo con el nombre especificado. Si el atributo no existe o alguno de los parámetros es
 * <code>null</code>, entonces se devolverá <code>null</code>.
 *
 * @param node {Node}   El nodo del que se quiere recupera el atributo.
 * @param name {String} Nombre del atributo.
 *
 * @return  {String} El valor del atributo del nodo con el nombre especificado o <code>null</code> si no existe.
 */
dome.Dom.getAttributeValue = function (node, name) {
   if (node == null || name == null) { return null; }

   var attr = dome.Dom.getAttribute(node, name);

   if (attr != null) {
      return attr.value;
   } else {
      return null;
   }
};

/**
 * Devuelve el nodo hijo con el nombre especificado. Si el nodo hijo no existe o se pasa algún parámetro a
 * <code>null</code>, entonces la función retorna <code>null</code>. En el caso de que exista más de un hijo con el
 * mismo nombre, entonces se devovlerá el primero de ellos.
 *
 * @param node {Node}   Nodo del que se queire recuperar el hijo.
 * @param name {String} Nombre del hijo.
 *
 * @return {Node} El nodo hijo con el nombre especificado o <code>null</code> si no se encuentra.
 */
dome.Dom.getChild = function (node, name) {
   if (node == null || name == null) { return null; }

   var list = node.getElementsByTagName(name); /* ToDO: Revisar si es correcto. Creo que devuelve a cualquier descendiento, no sólo hijo. */
   return (list != null ? list[0] : null);
};

/**
 * Devuelve un <code>Array</code> con los hijos, de tipo element, del nodo especificado que tengan el nombre
 * indicado, o todos si el nombre es <code>null</code>. Si no se encuentra ningún hijo se devuelve un
 * <code>Array</code> vacío. Si el nodo especificado es <code>null</code>, entonces se retorna <code>null</code>.
 *
 * @param node {Node}   Nodo del que se queire recuperar el hijo.
 * @param name {String} Nombre del hijo o <code>null</code> si se quiere recuperar todos.
 *
 * @return {Array}   Un vector con los nodos hijo de elemento.
 */
dome.Dom.getChildren = function (node, name) {
   if (node == null) { return null; }

   var list = node.childNodes;
   if (list != null) {
      var child = new Array();
      var n = 0;
      for (var i = 0; i < list.length; i++) {
         if ((list[i].nodeType == dome.Dom.ELEMENT_NODE) && (name == null || name == list[i].nodeName)) {
            child[n++] = list[i];
         }
      }
      return child;
   } else {
      return new Array();
   }
};

/**
 * Devuelve el valor del nodo hijo con el nombre especificado. Si el nodo hijo no existe o se pasa algún parámetro a
 * <code>null</code>, entonces la función retorna <code>null</code>. En el caso de que exista más de un hijo con el
 * mismo nombre, entonces se devovlerá el valor del primero de ellos.
 *
 * @param node {Node}   Nodo del que se queire recuperar el hijo.
 * @param name {String} Nombre del hijo.
 *
 * @return {String} El valor del nodo hijo con el nombre especificado o <code>null</code> si no se encuentra.
 */
dome.Dom.getChildText = function (node, name) {
   if (node == null || name == null) { return null; }

   var child = dome.Dom.getChild(node, name);
   if (child != null) {
      return dome.Dom.getText(child);
   } else {
      return null;
   }
};

/**
 * Devuelve el valor del nodo hijo con el nombre especificado sin los espacios del principo y final. Si el nodo hijo
 * no existe o se pasa algún parámetro a <code>null</code>, entonces la función retorna <code>null</code>. En el caso
 * de que exista más de un hijo con el mismo nombre, entonces se devovlerá el valor del primero de ellos.
 *
 * @param node {Node}   Nodo del que se queire recuperar el hijo.
 * @param name {String} Nombre del hijo.
 *
 * @return {String} El valor del nodo hijo con el nombre especificado o <code>null</code> si no se encuentra.
 */
dome.Dom.getChildTextTrim = function (node, name) {
   if (node == null || name == null) { return null; }

   var text = dome.Dom.getChildText(node, name);
   if (text != null) { text = text.replace(/^\s+/, '').replace(/\s+$/, ''); }

   return text;
};

/**
 * Devuelve el elemento raíz del documento. Si el parámetro especificado es <code>null</code> o no es del tipo
 * <code>DOCUMENT_NODE</code> este método devuelve <code>null</code>.
 *
 * @param {Document} doc Documento DOM.
 *
 * @return {Element} Elemento raíz del documento.
 */
dome.Dom.getDocumentElement = function (doc) {
   if (doc == null || doc.nodeType != dome.Dom.DOCUMENT_NODE) { return null; }
   return doc.documentElement;
};

/**
 * Devuelve el primer hijo, de tipo element, del nodo indicado. Si no se indica ningún nodo o éste no tiene hijos,
 * entonces se devuelve <code>null</code>.
 *
 * @param node {Node}   Nodo del que se quiere recuperar el primer hijo.
 *
 * @return {Node} El primer hijo del nodo especificado o <code>null</code> si no existe.
 */
dome.Dom.getFirstChild = function (node) {
   if (node == null) { return null; }

   var list = node.childNodes;
   if (list != null) {
      for (var i = 0; i < list.length; i++) {
         if (list[i].nodeType == dome.Dom.ELEMENT_NODE) { return list[i]; }
      }
   }

   return null;
};

/**
 * Devuelve el nombre del nodo indicado. Si no se indica ningún nodo, entonces se devuelve <code>null</code>.
 *
 * @param node {Node}   Nodo del que se quiere recuperar el nombre.
 *
 * @return {String}  Nombre del nodo indicado.
 */
dome.Dom.getName = function (node) {
   if (node == null) { return null; }

   return node.nodeName;

};

/**
 * Devuelve el valor del nodo indicado. Si el nodo es <code>null</code>, entonces se retorna <code>null</code>.
 *
 * @param node {Node}   Nodo del que se quiere recuperar su valor.
 *
 * @return {String}  Valor del nodo indicado.
 */
dome.Dom.getText = function (node) {
   if (node == null) { return null; }

   if (node.nodeType == dome.Dom.ELEMENT_NODE) {
      if (node.hasChildNodes() && (node.firstChild.nodeType == dome.Dom.TEXT_NODE || node.firstChild.nodeType == dome.Dom.CDATA_SECTION_NODE)) {
         /* W3C definition */
         return node.firstChild.nodeValue;
      } else if (node.text) {
         /* IE */
         return (!node.hasChildNodes() ? node.text : "");
      }
   } else if (node.nodeType == dome.Dom.ATTRIBUTE_NODE) {
      return node.nodeValue;
   } else if (node.nodeType == dome.Dom.TEXT_NODE || node.nodeType == dome.Dom.CDATA_SECTION_NODE ) {
      return node.nodeValue;
   }

   return "";
};

/**
 * Devuelve el valor del nodo indicado sin los espacios del principo y final. Si el nodo es <code>null</code>,
 * entonces se retorna <code>null</code>.
 *
 * @param node {Node}   Nodo del que se quiere recuperar su valor.
 *
 * @return {String}  Valor del nodo indicado.
 */
dome.Dom.getTextTrim = function (node) {
   if (node == null) { return null; }

   var text = dome.Dom.getText(node);
   if (text != null) { text = text.replace(/^\s+/, '').replace(/\s+$/, ''); }

   return text;
};

/**
 * Comprueba si el nodo indicado tiene hijos. Si el nodo es <code>null</code>, entonces retorna <code>false</code>.
 *
 * @param node {Node}   Nodo del que se quiere comprobar si tiene nodos hijo.
 *
 * @return {boolean} <code>true</code> si el nodo tiene algún hijo, <code>false</code> en caso contrario.
 */
dome.Dom.hasChildren = function (node) {
   return dome.Dom.getFirstChild(node) != null;
};

/**
 * Elimina el nodo <code>child</code> de la lista de hijos del nodo indicado en <code>node</code>. Si el
 * nodo <code>child</code> no existe no se hace nada.
 *
 * @param {Node} node      El nodo de donde se quiere eliminar el elemento.
 * @param {Node} child     El nodo que se quiere eliminar.
 */
dome.Dom.removeChild = function (node, child) {
   if (node != null && child != null) {
      node.removeChild(child);
   }
};

/**
 * Elimina todos los hijos del nodo indicado. Si el nodo es <code>null</code> no se hace nada.
 *
 * @param node {Node}   Nodo al que se le quieren eliminar todos los hijos.
 */
dome.Dom.removeChildren = function (node) {
   if (node != null) {
      while (node.firstChild) {
         node.removeChild(node.firstChild);
      }
   }
};
