/**

* Module dependencies
*/

var serialize = require('dom-serializer'),

select = require('css-select'),
parse = require('./parse'),
_ = {
  merge: require('lodash.merge'),
  defaults: require('lodash.defaults')
};

/**

* $.load(str)
*/

exports.load = function(content, options) {

var Cheerio = require('./cheerio');

options = _.defaults(options || {}, Cheerio.prototype.options);

var root = parse(content, options);

var initialize = function(selector, context, r, opts) {
  if (!(this instanceof initialize)) {
    return new initialize(selector, context, r, opts);
  }
  opts = _.defaults(opts || {}, options);
  return Cheerio.call(this, selector, context, r || root, opts);
};

// Ensure that selections created by the "loaded" `initialize` function are
// true Cheerio instances.
initialize.prototype = Object.create(Cheerio.prototype);
initialize.prototype.constructor = initialize;

// Mimic jQuery's prototype alias for plugin authors.
initialize.fn = initialize.prototype;

// Keep a reference to the top-level scope so we can chain methods that implicitly
// resolve selectors; e.g. $("<span>").(".bar"), which otherwise loses ._root
initialize.prototype._originalRoot = root;

// Add in the static methods
_.merge(initialize, exports);

// Add in the root
initialize._root = root;
// store options
initialize._options = options;

return initialize;

};

/*

*/

function render(that, dom, options) {

if (!dom) {
  if (that._root && that._root.children) {
    dom = that._root.children;
  } else {
    return '';
  }
} else if (typeof dom === 'string') {
  dom = select(dom, that._root, options);
}

return serialize(dom, options);

}

/**

* $.html([selector | dom], [options])
*/

exports.html = function(dom, options) {

var Cheerio = require('./cheerio');

// be flexible about parameters, sometimes we call html(),
// with options as only parameter
// check dom argument for dom element specific properties
// assume there is no 'length' or 'type' properties in the options object
if (Object.prototype.toString.call(dom) === '[object Object]' && !options && !('length' in dom) && !('type' in dom))
{
  options = dom;
  dom = undefined;
}

// sometimes $.html() used without preloading html
// so fallback non existing options to the default ones
options = _.defaults(options || {}, this._options, Cheerio.prototype.options);

return render(this, dom, options);

};

/**

* $.xml([selector | dom])
*/

exports.xml = function(dom) {

var options = _.defaults({xmlMode: true}, this._options);

return render(this, dom, options);

};

/**

* $.text(dom)
*/

exports.text = function(elems) {

if (!elems) {
  elems = this.root();
}

var ret = '',
    len = elems.length,
    elem;

for (var i = 0; i < len; i++) {
  elem = elems[i];
  if (elem.type === 'text') ret += elem.data;
  else if (elem.children && elem.type !== 'comment') {
    ret += exports.text(elem.children);
  }
}

return ret;

};

/**

* $.parseHTML(data [, context ] [, keepScripts ])
* Parses a string into an array of DOM nodes. The `context` argument has no
* meaning for Cheerio, but it is maintained for API compatibility with jQuery.
*/

exports.parseHTML = function(data, context, keepScripts) {

var parsed;

if (!data || typeof data !== 'string') {
  return null;
}

if (typeof context === 'boolean') {
  keepScripts = context;
}

parsed = this.load(data);
if (!keepScripts) {
  parsed('script').remove();
}

// The `children` array is used by Cheerio internally to group elements that
// share the same parents. When nodes created through `parseHTML` are
// inserted into previously-existing DOM structures, they will be removed
// from the `children` array. The results of `parseHTML` should remain
// constant across these operations, so a shallow copy should be returned.
return parsed.root()[0].children.slice();

};

/**

* $.root()
*/

exports.root = function() {

return this(this._root);

};

/**

* $.contains()
*/

exports.contains = function(container, contained) {

// According to the jQuery API, an element does not "contain" itself
if (contained === container) {
  return false;
}

// Step up the descendants, stopping when the root element is reached
// (signaled by `.parent` returning a reference to the same object)
while (contained && contained !== contained.parent) {
  contained = contained.parent;
  if (contained === container) {
    return true;
  }
}

return false;

};