/* mutationEventRegistry.data[k] = {
 *   listener: nsIDOMEventListener,
 *   useCapture: Boolean, true if listener should fire during capture
 * }
 */
const mutationEventRegistry = new DataRegistry();
mutationEventRegistry.requiredFields = ["listener", "useCapture"];
mutationEventRegistry.addProperties({
  /**
   * Add an event listener to a given document for every type of mutation.
   *
   * @param aDocument   The document receiving the event listener.
   * @param aListener   The listener being added.
   * @param aUseCapture Boolean, true if listener should fire during capture.
   */
  addListenersByDocument: function addListeners(aDocument, aListener, aUseCapture) {
    const events = ["DOMAttrModified",
                    "DOMCharacterDataModified",
                    "DOMNodeInserted",
                    "DOMNodeRemoved",
/* XXX ajvincent These two events demand a lot.  We probably don't need them...
                    "DOMNodeInsertedIntoDocument",
                    "DOMNodeRemovedFromDocument",
 */
                    "DOMElementNameChanged",
                    "DOMAttributeNameChanged",
                    "DOMSubtreeModified"];

    for each (var evt in events) {
      aDocument.addEventListener(evt, aListener, aUseCapture);
    }
  },

  /**
   * Remove an event listener to a given document for every type of mutation.
   *
   * @param aDocument   The document discarding the event listener.
   * @param aListener   The listener being removed.
   * @param aUseCapture Boolean, true if listener should fire during capture.
   */
  removeListenersByDocument: function removeListeners(aDocument, aListener, aUseCapture) {
    const events = ["DOMAttrModified",
                    "DOMCharacterDataModified",
                    "DOMNodeInserted",
                    "DOMNodeRemoved",
/* XXX ajvincent These two events demand a lot.  We probably don't need them...
                    "DOMNodeInsertedIntoDocument",
                    "DOMNodeRemovedFromDocument",
 */
                    "DOMElementNameChanged",
                    "DOMAttributeNameChanged",
                    "DOMSubtreeModified"];

    for each (var evt in events) {
      aDocument.removeEventListener(evt, aListener, aUseCapture);
    }
  },

  /**
   * Add an event listener to all registered documents.
   *
   * @param aListener   The listener being added.
   * @param aUseCapture Boolean, true if listener should fire during capture.
   */
  addEventListener: function addListener(aListener, aUseCapture) {
    this.addEntryByArguments("listener", aListener,
                             "useCapture", aUseCapture);

    // Apply to every XML document in the document registry.
    if ((typeof gDocumentPack == "undefined") || !gDocumentPack) {
      return;
    }

    for (var i = 0; i < gDocumentPack.length; i++) {
      var wrapper = gDocumentPack.item(i);
      if (wrapper instanceof xeIXMLDocumentWrapper) {
        this.addListenersByDocument(wrapper.wrappedDocument,
                                    aListener,
                                    aUseCapture);
      }
    }
  },

  /**
   * Remove an event listener to all registered documents.
   *
   * @param aListener   The listener being removed.
   * @param aUseCapture Boolean, true if listener should fire during capture.
   */
  removeEventListener: function removeListener(aListener, aUseCapture) {
    var result = this.searchFirstEntry({
      listener: aListener,
      useCapture: aUseCapture
    });

    this.check(result);
    this.removeEntry(result);

    // Apply to every XML document in the document registry.
    if ((typeof gDocumentPack == "undefined") || !gDocumentPack) {
      return;
    }

    for (var i = 0; i < gDocumentPack.length; i++) {
      var wrapper = gDocumentPack.item(i);
      if (wrapper instanceof xeIXMLDocumentWrapper) {
        this.removeListenersByDocument(wrapper.wrappedDocument,
                                       aListener,
                                       aUseCapture);
      }
    }
  },

  /**
   * Add the already-registered event listeners to a given document.
   *
   * @param aDocument   The document receiving the event listener.
   */
  registerWrapper: function registerDocument(aDocumentWrapper) {
    var doc = aDocumentWrapper.wrappedDocument;
    for each (var dataObj in this.data) {
      this.addListenersByDocument(doc, dataObj.listener, dataObj.useCapture);
    }
  },

  /**
   * Remove the already-registered event listeners for a given document wrapper.
   *
   * @param aWrapper The XML document wrapper to unregister.
   */
  unregisterWrapper: function unregisterDocument(aDocumentWrapper) {
    var doc = aDocumentWrapper.wrappedDocument;
    for each (var dataObj in this.data) {
      this.removeListenersByDocument(doc, dataObj.listener, dataObj.useCapture);
    }
  }
});

const mutationEventNotifier = {
  handleEvent: function handleEvent(aEvent) {
    const xeIXMLDocumentWrapper = Components.interfaces.xeIXMLDocumentWrapper;
    var doc = aEvent.target.ownerDocument;

    // Get the wrapper.
    var wrapper = null;
    for (var i = 0; i < gDocumentPack.length; i++) {
      wrapper = gDocumentPack.item(i);
      if ((wrapper instanceof xeIXMLDocumentWrapper) &&
          (wrapper.wrappedDocument == doc)) {
        break;
      }
      wrapper = null;
    }
    assert(wrapper, "No wrapper?");

    // Iterate through the applicable viewer tabs and notify them.
    var tab = documentViewer.viewTabs.firstChild;
    while (tab) {
      var viewer = documentViewer.getViewer(wrapper, tab, false);
      if (viewer) {
        viewer.notifyEvent("mutation", aEvent, null, null);
      }

      tab = tab.nextSibling;
    }
  }
}
mutationEventRegistry.addEventListener(mutationEventNotifier, true);
