"use strict";

var Node = require('./Node');
var LinkedList = require('./LinkedList');

var createDocumentFragmentFromArguments = function(document, args) {
  var docFrag = document.createDocumentFragment();

  for (var i=0; i<args.length; i++) {
    var argItem = args[i];
    var isNode = argItem instanceof Node;
    docFrag.appendChild(isNode ? argItem :
                        document.createTextNode(String(argItem)));
  }

  return docFrag;
};

// The ChildNode interface contains methods that are particular to `Node`
// objects that can have a parent.  It is implemented by `Element`,
// `DocumentType`, and `CharacterData` objects.
var ChildNode = {

  // Inserts a set of Node or String objects in the children list of this
  // ChildNode's parent, just after this ChildNode.  String objects are
  // inserted as the equivalent Text nodes.
  after: { value: function after() {
    var argArr = Array.prototype.slice.call(arguments);
    var parentNode = this.parentNode, nextSibling = this.nextSibling;
    if (parentNode === null) { return; }
    // Find "viable next sibling"; that is, next one not in argArr
    while (nextSibling && argArr.some(function(v) { return v===nextSibling; }))
      nextSibling = nextSibling.nextSibling;
    // ok, parent and sibling are saved away since this node could itself
    // appear in argArr and we're about to move argArr to a document fragment.
    var docFrag = createDocumentFragmentFromArguments(this.doc, argArr);

    parentNode.insertBefore(docFrag, nextSibling);
  }},

  // Inserts a set of Node or String objects in the children list of this
  // ChildNode's parent, just before this ChildNode.  String objects are
  // inserted as the equivalent Text nodes.
  before: { value: function before() {
    var argArr = Array.prototype.slice.call(arguments);
    var parentNode = this.parentNode, prevSibling = this.previousSibling;
    if (parentNode === null) { return; }
    // Find "viable prev sibling"; that is, prev one not in argArr
    while (prevSibling && argArr.some(function(v) { return v===prevSibling; }))
      prevSibling = prevSibling.previousSibling;
    // ok, parent and sibling are saved away since this node could itself
    // appear in argArr and we're about to move argArr to a document fragment.
    var docFrag = createDocumentFragmentFromArguments(this.doc, argArr);

    var nextSibling =
        prevSibling ? prevSibling.nextSibling : parentNode.firstChild;
    parentNode.insertBefore(docFrag, nextSibling);
  }},

  // Remove this node from its parent
  remove: { value: function remove() {
    if (this.parentNode === null) return;

    // Send mutation events if necessary
    if (this.doc) {
      this.doc._preremoveNodeIterators(this);
      if (this.rooted) {
        this.doc.mutateRemove(this);
      }
    }

    // Remove this node from its parents array of children
    // and update the structure id for all ancestors
    this._remove();

    // Forget this node's parent
    this.parentNode = null;
  }},

  // Remove this node w/o uprooting or sending mutation events
  // (But do update the structure id for all ancestors)
  _remove: { value: function _remove() {
    var parent = this.parentNode;
    if (parent === null) return;
    if (parent._childNodes) {
      parent._childNodes.splice(this.index, 1);
    } else if (parent._firstChild === this) {
      if (this._nextSibling === this) {
        parent._firstChild = null;
      } else {
        parent._firstChild = this._nextSibling;
      }
    }
    LinkedList.remove(this);
    parent.modify();
  }},

  // Replace this node with the nodes or strings provided as arguments.
  replaceWith: { value: function replaceWith() {
    var argArr = Array.prototype.slice.call(arguments);
    var parentNode = this.parentNode, nextSibling = this.nextSibling;
    if (parentNode === null) { return; }
    // Find "viable next sibling"; that is, next one not in argArr
    while (nextSibling && argArr.some(function(v) { return v===nextSibling; }))
      nextSibling = nextSibling.nextSibling;
    // ok, parent and sibling are saved away since this node could itself
    // appear in argArr and we're about to move argArr to a document fragment.
    var docFrag = createDocumentFragmentFromArguments(this.doc, argArr);
    if (this.parentNode === parentNode) {
      parentNode.replaceChild(docFrag, this);
    } else {
      // `this` was inserted into docFrag
      parentNode.insertBefore(docFrag, nextSibling);
    }
  }},

};

module.exports = ChildNode;
