text: 'use strict';
  
  var Doctype = require('../common/doctype');
  
  //Conversion tables for DOM Level1 structure emulation
  var nodeTypes = {
      element: 1,
      text: 3,
      cdata: 4,
      comment: 8
  };
  
  var nodePropertyShorthands = {
      tagName: 'name',
      childNodes: 'children',
      parentNode: 'parent',
      previousSibling: 'prev',
      nextSibling: 'next',
      nodeValue: 'data'
  };
  
  //Node
  var Node = function (props) {
      for (var key in props) {
          if (props.hasOwnProperty(key))
              this[key] = props[key];
      }
  };
  
  Node.prototype = {
      get firstChild() {
          var children = this.children;
          return children && children[0] || null;
      },
  
      get lastChild() {
          var children = this.children;
          return children && children[children.length - 1] || null;
      },
  
      get nodeType() {
          return nodeTypes[this.type] || nodeTypes.element;
      }
  };
  
  Object.keys(nodePropertyShorthands).forEach(function (key) {
      var shorthand = nodePropertyShorthands[key];
  
      Object.defineProperty(Node.prototype, key, {
          get: function () {
              return this[shorthand] || null;
          },
          set: function (val) {
              this[shorthand] = val;
              return val;
          }
      });
  });
  
  
  //Node construction
  exports.createDocument =
  exports.createDocumentFragment = function () {
      return new Node({
          type: 'root',
          name: 'root',
          parent: null,
          prev: null,
          next: null,
          children: []
      });
  };
  
  exports.createElement = function (tagName, namespaceURI, attrs) {
      var attribs = {},
          attribsNamespace = {},
          attribsPrefix = {};
  
      for (var i = 0; i < attrs.length; i++) {
          var attrName = attrs[i].name;
  
          attribs[attrName] = attrs[i].value;
          attribsNamespace[attrName] = attrs[i].namespace;
          attribsPrefix[attrName] = attrs[i].prefix;
      }
  
      return new Node({
          type: tagName === 'script' || tagName === 'style' ? tagName : 'tag',
          name: tagName,
          namespace: namespaceURI,
          attribs: attribs,
          'x-attribsNamespace': attribsNamespace,
          'x-attribsPrefix': attribsPrefix,
          children: [],
          parent: null,
          prev: null,
          next: null
      });
  };
  
  exports.createCommentNode = function (data) {
      return new Node({
          type: 'comment',
          data: data,
          parent: null,
          prev: null,
          next: null
      });
  };
  
  var createTextNode = function (value) {
      return new Node({
          type: 'text',
          data: value,
          parent: null,
          prev: null,
          next: null
      });
  };
  
  
  //Tree mutation
  exports.setDocumentType = function (document, name, publicId, systemId) {
      var data = Doctype.serializeContent(name, publicId, systemId),
          doctypeNode = null;
  
      for (var i = 0; i < document.children.length; i++) {
          if (document.children[i].type === 'directive' && document.children[i].name === '!doctype') {
              doctypeNode = document.children[i];
              break;
          }
      }
  
      if (doctypeNode) {
          doctypeNode.data = data;
          doctypeNode['x-name'] = name;
          doctypeNode['x-publicId'] = publicId;
          doctypeNode['x-systemId'] = systemId;
      }
  
      else {
          appendChild(document, new Node({
              type: 'directive',
              name: '!doctype',
              data: data,
              'x-name': name,
              'x-publicId': publicId,
              'x-systemId': systemId
          }));
      }
  
  };
  
  exports.setQuirksMode = function (document) {
      document.quirksMode = true;
  };
  
  exports.isQuirksMode = function (document) {
      return document.quirksMode;
  };
  
  var appendChild = exports.appendChild = function (parentNode, newNode) {
      var prev = parentNode.children[parentNode.children.length - 1];
  
      if (prev) {
          prev.next = newNode;
          newNode.prev = prev;
      }
  
      parentNode.children.push(newNode);
      newNode.parent = parentNode;
  };
  
  var insertBefore = exports.insertBefore = function (parentNode, newNode, referenceNode) {
      var insertionIdx = parentNode.children.indexOf(referenceNode),
          prev = referenceNode.prev;
  
      if (prev) {
          prev.next = newNode;
          newNode.prev = prev;
      }
  
      referenceNode.prev = newNode;
      newNode.next = referenceNode;
  
      parentNode.children.splice(insertionIdx, 0, newNode);
      newNode.parent = parentNode;
  };
  
  exports.detachNode = function (node) {
      if (node.parent) {
          var idx = node.parent.children.indexOf(node),
              prev = node.prev,
              next = node.next;
  
          node.prev = null;
          node.next = null;
  
          if (prev)
              prev.next = next;
  
          if (next)
              next.prev = prev;
  
          node.parent.children.splice(idx, 1);
          node.parent = null;
      }
  };
  
  exports.insertText = function (parentNode, text) {
      var lastChild = parentNode.children[parentNode.children.length - 1];
  
      if (lastChild && lastChild.type === 'text')
          lastChild.data += text;
      else
          appendChild(parentNode, createTextNode(text));
  };
  
  exports.insertTextBefore = function (parentNode, text, referenceNode) {
      var prevNode = parentNode.children[parentNode.children.indexOf(referenceNode) - 1];
  
      if (prevNode && prevNode.type === 'text')
          prevNode.data += text;
      else
          insertBefore(parentNode, createTextNode(text), referenceNode);
  };
  
  exports.adoptAttributes = function (recipientNode, attrs) {
      for (var i = 0; i < attrs.length; i++) {
          var attrName = attrs[i].name;
  
          if (typeof recipientNode.attribs[attrName] === 'undefined') {
              recipientNode.attribs[attrName] = attrs[i].value;
              recipientNode['x-attribsNamespace'][attrName] = attrs[i].namespace;
              recipientNode['x-attribsPrefix'][attrName] = attrs[i].prefix;
          }
      }
  };
  
  
  //Tree traversing
  exports.getFirstChild = function (node) {
      return node.children[0];
  };
  
  exports.getChildNodes = function (node) {
      return node.children;
  };
  
  exports.getParentNode = function (node) {
      return node.parent;
  };
  
  exports.getAttrList = function (node) {
      var attrList = [];
  
      for (var name in node.attribs) {
          if (node.attribs.hasOwnProperty(name)) {
              attrList.push({
                  name: name,
                  value: node.attribs[name],
                  namespace: node['x-attribsNamespace'][name],
                  prefix: node['x-attribsPrefix'][name]
              });
          }
      }
  
      return attrList;
  };
  
  
  //Node data
  exports.getTagName = function (element) {
      return element.name;
  };
  
  exports.getNamespaceURI = function (element) {
      return element.namespace;
  };
  
  exports.getTextNodeContent = function (textNode) {
      return textNode.data;
  };
  
  exports.getCommentNodeContent = function (commentNode) {
      return commentNode.data;
  };
  
  exports.getDocumentTypeNodeName = function (doctypeNode) {
      return doctypeNode['x-name'];
  };
  
  exports.getDocumentTypeNodePublicId = function (doctypeNode) {
      return doctypeNode['x-publicId'];
  };
  
  exports.getDocumentTypeNodeSystemId = function (doctypeNode) {
      return doctypeNode['x-systemId'];
  };
  
  
  //Node types
  exports.isTextNode = function (node) {
      return node.type === 'text';
  };
  
  exports.isCommentNode = function (node) {
      return node.type === 'comment';
  };
  
  exports.isDocumentTypeNode = function (node) {
      return node.type === 'directive' && node.name === '!doctype';
  };
  
  exports.isElementNode = function (node) {
      return !!node.attribs;
  };
  