var http          = require('../../builtins').http,
    HtmlToDom     = require('./htmltodom').HtmlToDom,
    domToHtml     = require('./domtohtml').domToHtml,
    htmlencoding  = require('./htmlencoding'),
    HTMLEncode    = htmlencoding.HTMLEncode,
    HTMLDecode    = htmlencoding.HTMLDecode,
    jsdom         = require('../../jsdom'),
    NOT_IMPLEMENTED = require('./utils').NOT_IMPLEMENTED,
    CSSStyleDeclaration = require('../../builtins').cssstyle.CSSStyleDeclaration,
    defineGetter  = require('../utils').defineGetter,
    defineSetter  = require('../utils').defineSetter,
    createFrom    = require('../utils').createFrom;

function matchesDontThrow(el, selector) {
  try {
    return el.matchesSelector(selector);
  } catch (e) {
    return false;
  }
}

//Caching for HTMLParser require. HUGE performace boost.
/**
* 5000 iterations
* Without cache: ~1800+ms
* With cache: ~80ms
*/
var defaultParser = null;
var getDefaultParser = exports.getDefaultParser = function () {
  if (defaultParser === null) {
    defaultParser = require('../../builtins').htmlparser2;
  }
  return defaultParser;
}

/**
 * Export getter/setter of default parser to facilitate testing
 * with different HTML parsers.
 */
exports.setDefaultParser = function (parser) {
  if (typeof parser == 'object') {
    defaultParser = parser;
  } else if (typeof parser == 'string')
    defaultParser = require(parser);
}

/**
 * Augments the given DOM by adding browser-specific properties and methods (BOM).
 * Returns the augmented DOM.
 */
var browserAugmentation = exports.browserAugmentation = function(dom, options) {

  if(!options) {
    options = {};
  }

  // set up html parser - use a provided one or try and load from library
  var parser = options.parser || getDefaultParser();

  if (dom._augmented && dom._parser === parser) {
    return dom;
  }

  dom._parser = parser;
  var htmltodom = new HtmlToDom(parser);

  if (!dom.HTMLDocument) {
    dom.HTMLDocument = dom.Document;
  }
  if (!dom.HTMLDocument.prototype.write) {
    dom.HTMLDocument.prototype.write = function(html) {
      this.innerHTML = html;
    };
  }

  dom.Element.prototype.getElementsByClassName = function(className) {

    function filterByClassName(child) {
      if (!child) {
        return false;
      }

      if (child.nodeType &&
          child.nodeType === dom.Node.ENTITY_REFERENCE_NODE)
      {
        child = child._entity;
      }

      var classString = child.className;
      if (classString) {
        var s = classString.split(" ");
        for (var i=0; i<s.length; i++) {
          if (s[i] === className) {
            return true;
          }
        }
      }
      return false;
    }

    return new dom.NodeList(this.ownerDocument || this, dom.mapper(this, filterByClassName));
  };

  defineGetter(dom.Element.prototype, 'sourceIndex', function() {
    /*
    * According to QuirksMode:
    * Get the sourceIndex of element x. This is also the index number for
    * the element in the document.getElementsByTagName('*') array.
    * http://www.quirksmode.org/dom/w3c_core.html#t77
    */
    var items = this.ownerDocument.getElementsByTagName('*'),
        len = items.length;

    for (var i = 0; i < len; i++) {
      if (items[i] === this) {
        return i;
      }
    }
  });

  defineGetter(dom.Document.prototype, 'outerHTML', function() {
    return domToHtml(this, true);
  });

  defineGetter(dom.Element.prototype, 'outerHTML', function() {
    return domToHtml(this, true);
  });

  defineGetter(dom.Element.prototype, 'innerHTML', function() {
    if (/^(?:script|style)$/.test(this._tagName)) {
      var type = this.getAttribute('type');
      if (!type || /^text\//i.test(type) || /\/javascript$/i.test(type)) {
        return domToHtml(this._childNodes, true, true);
      }
    }

    return domToHtml(this._childNodes, true);
  });

  defineSetter(dom.Element.prototype, 'doctype', function() {
    throw new dom.DOMException(dom.NO_MODIFICATION_ALLOWED_ERR);
  });
  defineGetter(dom.Element.prototype, 'doctype', function() {
    var r = null;
    if (this.nodeName == '#document') {
        if (this._doctype) {
         r = this._doctype;
        }
    }
    return r;
  });

  defineSetter(dom.Element.prototype, 'innerHTML', function(html) {
    //Clear the children first:
    var child;
    while ((child = this._childNodes[0])) {
      this.removeChild(child);
    }

    if (this.nodeName === '#document') {
      parseDocType(this, html);
    }
    if (html !== "" && html != null) {
      htmltodom.appendHtmlToElement(html, this);
    }
    return html;
  });


  defineGetter(dom.Document.prototype, 'innerHTML', function() {
    return domToHtml(this._childNodes, true);
  });

  defineSetter(dom.Document.prototype, 'innerHTML', function(html) {
    //Clear the children first:
    var child;
    while ((child = this._childNodes[0])) {
      this.removeChild(child);
    }

    if (this.nodeName === '#document') {
      parseDocType(this, html);
    }
    if (html !== "" && html != null) {
      htmltodom.appendHtmlToElement(html, this);
    }
    return html;
  });

  var DOC_HTML5      = /<!doctype html>/i,
      DOC_TYPE       = /<!DOCTYPE (\w(.|\n)*)">/i,
      DOC_TYPE_START = '<!DOCTYPE ',
      DOC_TYPE_END   = '">';

  function parseDocType(doc, html) {
    var publicID = '',
        systemID = '',
        fullDT = '',
        name = 'HTML',
        set = true,
        doctype = html.match(DOC_HTML5);

    //Default, No doctype === null
    doc._doctype = null;

    if (doctype && doctype[0]) { //Handle the HTML shorty doctype
      fullDT = doctype[0];
    } else { //Parse the doctype
      // find the start
      var start     = html.indexOf(DOC_TYPE_START),
          end       = html.indexOf(DOC_TYPE_END),
          docString;

      if (start < 0 || end < 0) {
        return;
      }

      docString = html.substr(start, (end-start)+DOC_TYPE_END.length);
      doctype = docString.replace(/[\n\r]/g,'').match(DOC_TYPE);

      if (!doctype) {
        return;
      }

      fullDT = doctype[0];
      doctype = doctype[1].split(' "');
      var _id1 = doctype.length ? doctype.pop().replace(/"/g, '') : '',
          _id2 = doctype.length ? doctype.pop().replace(/"/g, '') : '';

      if (_id1.indexOf('-//') !== -1) {
        publicID = _id1;
      }
      if (_id2.indexOf('-//') !== -1) {
        publicID = _id2;
      }
      if (_id1.indexOf('://') !== -1) {
        systemID = _id1;
      }
      if (_id2.indexOf('://') !== -1) {
        systemID = _id2;
      }
      if (doctype.length) {
        doctype = doctype[0].split(' ');
        name = doctype[0].toUpperCase();
      }
    }
    doc._doctype = new dom.DOMImplementation().createDocumentType(name, publicID, systemID);
    doc._doctype._ownerDocument = doc;
    doc._doctype._fullDT = fullDT;
    doc._doctype.toString = function() {
      return this._fullDT;
    };
  }

  dom.Document.prototype.getElementsByClassName = function(className) {

    function filterByClassName(child) {
      if (!child) {
        return false;
      }

      if (child.nodeType &&
          child.nodeType === dom.Node.ENTITY_REFERENCE_NODE)
      {
        child = child._entity;
      }

      var classString = child.className;
      if (classString) {
        var s = classString.split(" ");
        for (var i=0; i<s.length; i++) {
          if (s[i] === className) {
            return true;
          }
        }
      }
      return false;
    }

    return new dom.NodeList(this.ownerDocument || this, dom.mapper(this, filterByClassName));
  };

  defineGetter(dom.Element.prototype, 'nodeName', function(val) {
    return this._nodeName.toUpperCase();
  });

  defineGetter(dom.Element.prototype, 'tagName', function(val) {
    var t = this._tagName.toUpperCase();
    //Document should not return a tagName
    if (this.nodeName === '#document') {
      t = null;
    }
    return t;
  });

  dom.Element.prototype.scrollTop = 0;
  dom.Element.prototype.scrollLeft = 0;

  defineGetter(dom.Document.prototype, 'parentWindow', function() {
    return null;
  });

  defineSetter(dom.Document.prototype, 'parentWindow', function(window) {
    NOT_IMPLEMENTED(null,'parentWindow');
  });

  defineGetter(dom.Document.prototype, 'defaultView', function() {
    return this.parentWindow;
  });

  dom._augmented = true;
  return dom;
};
