/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Verbosio XML Editor.
 *
 * The Initial Developer of the Original Code is
 * Alexander J. Vincent <ajvincent@gmail.com>.
 * Portions created by the Initial Developer are Copyright (C) 2005
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var treeitemTemplate = null;

function assertXMLDoc(aWrapper) {
  assert(aWrapper == documentViewer.currentWrapper, "Check current wrapper!");
  const errMsg = "Inspector viewer method called on improper document!";
  assert(isInstance(aWrapper, "xeIXMLDocumentWrapper"), errMsg);
}

function getItemByDescendants(aIndexArray) {
  var treeitem = this.lastChild.firstChild;
  for (var i = 0; i < aIndexArray.length; i++) {
    var index = aIndexArray[i];
    treeitem = treeitem.lastChild.childNodes[index];
  }

  return treeitem;
}

function getViewerPanelForNode(aNode, vpContainer) {
  dump("aNode: " + aNode.nodeName + "\n");
  // The selector is the first child node.
  var iterator = vpContainer.firstChild;
  var panel = null;
  var maxFound = 0;
  var index = 0;

  while (iterator) {
    if (typeof iterator.getPriorityForNode != "function") {
      throw new Error("getPriority parsing failed for " + iterator.getAttribute('type'));
    }
    var priority = iterator.getPriorityForNode(aNode);
    vpContainer.setPanelDisabled(index, !priority);

    if (priority > maxFound) {
      panel = iterator;
      maxFound = priority;
    }

    iterator = iterator.nextSibling;
    index++;
  }

  return panel;
}

/**
 * Modify the selection or the document based on information from Verbosio.
 *
 * @param aType  String defining the event type.
 * @param aEvent nsIDOMEvent dispatched for master document (may be null)
 * @param aDescIndexes1 Descendant index array based on
 */
function notifyEvent(aType, aEvent, aDescIndexes1, aDescIndexes2) {
  dump("Entering inspectorOverlay.js's notifyEvent with type " + aType + "\n");
  // this == the viewer from getViewerNode().
  assertXMLDoc(this.documentWrapper);
  var wrapper = this.documentWrapper;
  var range;
  var startItem;
  var endItem;
  var startIndex;
  var endIndex;

  switch (aType) {
    case "select":
      // fall through; we want to do the same as if we had a focus call.

    case "focus":
      if (aDescIndexes1 && aDescIndexes2) {
        wrapper    = documentViewer.currentWrapper;
        startItem  = this.tree.getItemByDescendants(aDescIndexes1);
        endItem    = this.tree.getItemByDescendants(aDescIndexes2);
        startIndex = this.tree.view.getIndexOfItem(startItem);
        endIndex   = this.tree.view.getIndexOfItem(endItem);

/* Selection requires a special touch.  For character data nodes, the start
 * container is the closest we get on the node tree, and we should populate the
 * appropriate text entry box and select the right contents.
 *
 * For other nodes, we clear the text entry box and select right
 * down to that node.
 */
        var range          = wrapper.selectionRange;
        var startContainer = range.startContainer;
        var endContainer   = range.endContainer;
        var startOffset    = range.startOffset;
        var endOffset      = range.endOffset;

        var startIsCData = (isInstance(startContainer, "nsIDOMCharacterData") == 2);
        if (startIsCData) {

        }

        this.tree.view.selection.rangedSelect(startIndex, endIndex, false);
      }

      break;

    case "blur":
      // do nothing, selection's already handled.
      break;

    case "mutation":
      break;

    case "repaint":
      break;

    default:
      throw new Error("notifyEvent called with invalid value!");
  }
}

/**
 * Update the document wrapper's selection from the inspector tree.
 *
 * @param aEvent Event object with current target at the tree.
 */
function updateSelectionByTree(aEvent) {
  var tree = aEvent.currentTarget;
  var view = tree.view;
  var selection = view.selection;
  var rangeCount = selection.getRangeCount();
  dump("rangeCount: " + rangeCount + "\n");
  assert(rangeCount < 2, "Why aren't we blocking non-contiguous selections?");

  if (rangeCount == 0) {
    return;
  }

  // Get the selection.
  var min = {};
  var max = {};
  selection.getRangeAt(0, min, max);
  min = min.value;
  max = max.value;

  assert((min == -1) == (max == -1), "If one is -1, shouldn't the other be?");
  if (min == -1) {
    return;
  }

  // Fix up the document wrapper's range.
  var viewer = tree.parentNode.parentNode;
  var wrapper = viewer.documentWrapper;

  var wrapperRange = wrapper.selectionRange;
  var startNode = view.getItemAtIndex(min).getUserData("referenceNode");
  var endNode   = view.getItemAtIndex(max).getUserData("referenceNode");

  // Document, document fragment nodes can't go in the range.
  if (!startNode.parentNode) {
    wrapperRange.setStartBefore(startNode.firstChild);
  } else {
    wrapperRange.setStartBefore(startNode);
  }
  if (!endNode.parentNode) {
    wrapperRange.setEndAfter(endNode.lastChild);
  } else {
    wrapperRange.setEndAfter(endNode);
  }

  // Update the value panels.
  const nsIDOMCharacterData = Components.interfaces.nsIDOMCharacterData;

//  startNode = wrapperRange.startContainer;
  var startInText = (startNode instanceof nsIDOMCharacterData);

//  endNode = wrapperRange.endContainer;
  var endInText = (endNode instanceof nsIDOMCharacterData);

  var inSameNode = (startNode == endNode);
  var startPanel = getViewerPanelForNode(startNode, viewer.upperHalf);
  viewer.upperHalf.selectedPanel = startPanel;

  startPanel.showNode(startNode);
  if (startInText) {
    startPanel.selectText(wrapperRange.startOffset, startNode.data.length);
  } else if (startNode instanceof nsIDOMCharacterData) {
    startPanel.selectText(0, 0);
  }

  if (!inSameNode) {
    var endPanel = getViewerPanelForNode(endNode, viewer.lowerHalf);
    assert(endPanel, "No viewer panel???");
    viewer.lowerHalf.selectedPanel = endPanel;
    endPanel.showNode(endNode);
    if (endInText) {
      endPanel.selectText(0, wrapperRange.endOffset);
    } else if (endNode instanceof nsIDOMCharacterData) {
      endPanel.selectText(0, 0);
    }
  }

  viewer.lowerHalf.style.visibility = (inSameNode) ? "hidden" : "visible";

  // Finally, update our edit commands.
  baseEditController.updateCommands();
}

/**
 * Get a viewer node for a given document.
 *
 * @param aDocumentWrapper xeIDocumentWrapper needing a viewer.
 *
 * @return clone of inspectorView element.
 */
function getViewerNode(aDocumentWrapper) {
  if (isInstance(aDocumentWrapper, "xeIXMLDocumentWrapper") == 0)
    return null;

  // Create the node.
  dump("getViewerNode\n");
  var hboxView = document.getElementByAttrKey(uuid, "inspectorView");
  hboxView = hboxView.cloneNode(true);
  hboxView.removeAttribute("scopeid");
  hboxView.removeAttribute("localid");

  var inspectorTree = hboxView.getElementsByAttribute("localid", "inspectorTree")[0];
  assert(inspectorTree, "No inspector tree element?");

  var treechildren = inspectorTree.lastChild;
  treeitemTemplate = document.getElementByAttrKey(uuid, "inspectorTreeItem");
  dump("treeItemTemplate: " + treeitemTemplate + "\n");
  treeitemTemplate = treeitemTemplate.cloneNode(true);
  treeitemTemplate.removeAttribute("scopeid");
  treeitemTemplate.removeAttribute("localid");

  dump("treeitems: " + treechildren.getElementsByTagName("treeitem").length + "\n");

  hboxView.notifyEvent = notifyEvent;
  hboxView.tree = inspectorTree;
  hboxView.documentWrapper = aDocumentWrapper;
  hboxView.upperHalf = hboxView.getElementsByAttribute("localid", "upperHalf")[0];
  hboxView.lowerHalf = hboxView.getElementsByAttribute("localid", "lowerHalf")[0];
  inspectorTree.getItemByDescendants = getItemByDescendants;

  hboxView.initializeView = function initializeView() {
    // Initialize DOM tree here.
    var root = aDocumentWrapper.wrappedDocument;
    domTreeViewMaker.refreshTree(inspectorTree, root);
  }

  inspectorTree.addEventListener("select", updateSelectionByTree, false);
  return hboxView;
}

const nsISAXXMLReader = Components.interfaces.nsISAXXMLReader;

var saxReader = Components.classes["@mozilla.org/saxparser/xmlreader;1"]
                          .createInstance(nsISAXXMLReader);
var saxErrorHandler = {
  pass: false,

  reset: function reset() {
    this.pass = true;
  },

  fatalError: function fatalError(locator, error)
  {
    this.pass = false;
  },

  error: function error(locator, error)
  {
    this.pass = false;
  },

  ignorableWarning: function ignorableWarning(locator, error)
  {
    this.pass = false;
  }
};

saxReader.errorHandler = saxErrorHandler;

function initAttrPrecondition(aPre) {
  aPre.insertWhereMenu.setAttribute("disabled", "true");
  aPre.currentElementNode = aPre.getUserData("currentElementNode");

  aPre.namespaceSet = aPre.getElementsByAttribute("localid",
                                                  "new-attr-namespace-set")[0];
  var map = verbosio.getNamespacesMap(aPre);
  aPre.namespaceSet.updateNamespaceMap(aPre.currentElementNode);
  var elem = "<foo\n";
  for (var prop in map) {
    if (prop != "xmlns") {
      elem += "xmlns:" + prop + "='" + map[prop] + "'\n";
    }
  }
  elem += ">";

  // Set up important variables for quick XML parsing
  var doc = aPre.currentElementNode.ownerDocument;
  var menulistset = aPre.getElementsByAttribute('localid',
                                                'new-attr-namespace-set')[0];
  var textbox = aPre.getElementsByAttribute('localid', 'new-attr-name')[0];
  var textvalue = aPre.getElementsByAttribute('localid', 'new-attr-value')[0];
  var status = aPre.getElementsByAttribute("localid", "status")[0];

  var handler = {
    handleEvent: function handleEvent(aEvent) {
      var nsURI = menulistset._contentBox.firstChild.value;
      var prefix = menulistset._contentBox.lastChild.value;
      prefix = prefix ? prefix + ':' : '';
      var localName = textbox.value;
      var subElement = "<sub " + (prefix + localName) + "='";
      subElement += encodeURIComponent(textvalue.value) + "'/>";

      // Parse the XML!
      var xml = elem + subElement + "</foo>";
      saxErrorHandler.reset();
      saxReader.parseFromString(xml, "application/xml");
      if (saxErrorHandler.pass) {
        aPre.invalidMessage = null;
      } else {
        aPre.invalidMessage = "Invalid markup";
      }
    }
  }

  aPre.addEventListener("input", handler, true);

  aPre.pendingAction = {
    // xeITransaction
    preconditionLocalId: null,

    // xeITransaction
    description: "New attribute on element",

    // xeITransaction
    ownerWrapper: documentViewer.currentWrapper,

    // nsITransaction
    doTransaction: function doTransaction() {
      var nsURI = menulistset._contentBox.firstChild.value;
      var prefix = menulistset._contentBox.lastChild.value;
      prefix = prefix ? prefix + ':' : '';
      var localName = textbox.value;
      this.generatedAttrNode = doc.createAttributeNS(nsURI, prefix + localName);
      this.generatedAttrNode.nodeValue = textvalue.value;

      aPre.currentElementNode.setAttributeNode(this.generatedAttrNode);
    },

    // nsITransaction
    undoTransaction: function undoTransaction() {
      aPre.currentElementNode.removeAttributeNode(this.generatedAttrNode);
    },

    // nsITransaction
    redoTransaction: function redoTransaction() {
      aPre.currentElementNode.setAttributeNode(this.generatedAttrNode);
    },

    // nsITransaction
    isTransient: false,

    // nsITransaction
    merge: function merge(aTransaction) {
      return false;
    },

    // nsISupports
    QueryInterface: function QueryInterface(aIID) {
      if (aIID.equals(Components.interfaces.xeITransaction) ||
          aIID.equals(Components.interfaces.nsITransaction) ||
          aIID.equals(Components.interfaces.nsISupports)) {
        return this;
      }
      throw Components.results.NS_ERROR_NO_INTERFACE;
      return null;
    }
  }
}

// XXX ajvincent Localize!
window.addEventListener("load", function addInspectorTab(evt) {
  documentViewer.appendViewerTab("Inspector", uuid, "i");
}, true);
