/* ***** 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 Chrome Registry Contents.
 *
 * The Initial Developer of the Original Code is
 * Alexander J. Vincent <ajvincent@gmail.com>.
 * Portions created by the Initial Developer are Copyright (C) 2007
 * 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 ***** */
 
const dirTreeViewMaker = {
  /**
   * Recursively generate tree items for a given DOM node and a treechildren element.
   *
   * @param aTreeChildren    <xul:treechildren/> element to append children to.
   * @param aFileObject      JSLib Dir or File object to read.
   * @param treeitemTemplate <xul:treeitem/> to use as a template.
   * @param beforeItem       Item to insert the newly created item before.
   * @param aDepth           Depth to open grandchildren elements.
   */
  addToChildren: function addToChildren(aTreeChildren,
                                        aFileObject,
                                        treeitemTemplate,
                                        beforeItem,
                                        aDepth)
  {
    assert(aFileObject instanceof Dir || aFileObject instanceof File, "Check caller!");
    aFileObject.wrappedJSObject = aFileObject;
    var treeitem = treeitemTemplate.cloneNode(true);
    treeitem.ownerTree = treeitemTemplate.ownerTree;
    treeitem.setUserData("referenceFile", aFileObject, null);
    aFileObject.treeitemNode = treeitem;
    var cell = treeitem.firstChild.firstChild;

    var cols = treeitemTemplate.ownerTree.propertyCols;
    for (var i = 0; i < cols.length; i++)
    {
      if (cols[i] && aFileObject[cols[i]]) {
        cell.setAttribute("label", aFileObject[cols[i]]);
        cell = cell.nextSibling;
      }
    }

    // Handle descendants
    if (aFileObject instanceof Dir)
    {
      treeitem.setAttribute("container", "true");
      if (aDepth > 0)
      {
        treeitem.setAttribute("open", "true");
        var contents = aFileObject.readDir();
        contents.sort();

        for (var i = 0; i < contents.length; i++)
        {
          var child = contents[i];
          this.addToChildren(treeitem.lastChild, child, treeitemTemplate, null, aDepth - 1);
        }
      }
    }

    aTreeChildren.insertBefore(treeitem, beforeItem);
  },

  /**
   * Get the directory reading for a chrome base URI.
   *
   * @param aParentItem Tree item for the chrome package.
   * @param aBaseURI    Chrome base URI to generate a Dir tree from.
   */
  addBaseURIDir: getContractFunction({
    precondition: function addBaseURIDir_pre(aParentItem, aBaseURI) {
      assert(aParentItem.namespaceURI == XUL_NS, "Not a XUL treeitem?");
      assert(aParentItem.localName == "treeitem", "Not a XUL treeitem?");

      var grandParent = aParentItem.parentNode.parentNode;
      assert(grandParent.localName == "treeitem", "Not a XUL treeitem?");
      var cell = grandParent.firstChild.firstChild;
      var path = "chrome://" + cell.getAttribute("label") + "/";
      
      cell = aParentItem.firstChild.firstChild;
      path += cell.getAttribute("label") + "/";

      assert(path == aBaseURI.getURI("chrome"), "Base URI didn't match!");
    },

    body: function addBaseURIDir(aParentItem, aBaseURI) {
      const localPath = aBaseURI.getURI("local");
      const treechildren = aParentItem.lastChild;
      const itemTemplate = aParentItem.ownerTree.getUserData("itemTemplate");

      aParentItem.setAttribute("container", "true");
      aParentItem.setAttribute("open", "true");

      if (localPath.indexOf("jar:") == 0) {
        /* Jarred chrome isn't supported yet.  In order to edit it, we need to
           unzip the chrome package.  We'll do this at a later date.
         */
        var treeitem = itemTemplate.cloneNode(true);
        treeitem.ownerTree = aParentItem.ownerTree;
        var cell = treeitem.firstChild.firstChild;
        cell.setAttribute("label", "(Jarred chrome)");
        cell.setAttribute("style", "font-style: italic;");
        treechildren.appendChild(treeitem);
        return;
      }

      var localDir = new Dir(localPath);
      assert(localDir.isDir(), "Not a directory?");
      var localContents = localDir.readDir();
      for each (var fileObj in localContents) {
        this.addToChildren(treechildren,
                           fileObj,
                           itemTemplate,
                           null,
                           0);
      }
    }
  }),

  /**
   * Add chrome package types to a tree item.
   *
   * @param aParentItem  Tree item for the package name.
   * @param aPackageName The name of the chrome package.
   */
  addPackageTypes: getContractFunction({
    precondition: function addPackageTypes_pre(aParentItem, aPackageName) {
      assert(aParentItem.namespaceURI == XUL_NS, "Not a XUL tree item?");
      assert(aParentItem.localName == "treeitem", "Not a XUL tree item?");
      assert(aParentItem.ownerTree, "No owner tree?");
    },

    body: function addPackageTypes(aParentItem, aPackageName) {
      const ownerTree = aParentItem.ownerTree;
      const itemTemplate = ownerTree.getUserData("itemTemplate");
      const treechildren = aParentItem.lastChild;
      const cols = ownerTree.propertyCols;

      const xeIChromeDocumentPack = Components.interfaces.xeIChromeDocumentPack;
      const PACKAGE_TYPE_CONTENT  = xeIChromeDocumentPack.PACKAGE_TYPE_CONTENT;
      const PACKAGE_TYPE_LOCALE   = xeIChromeDocumentPack.PACKAGE_TYPE_LOCALE;
      const PACKAGE_TYPE_SKIN     = xeIChromeDocumentPack.PACKAGE_TYPE_SKIN;

      var treeitem = null;
      var baseURI = null;
      var subPackage = null;
      var i;
      var cell = null;

      // Content first.
      var aCount = {};
      var array = gDocumentPack.getSubPackageNames(aPackageName,
                                                   PACKAGE_TYPE_CONTENT,
                                                   aCount);
      if (aCount.value == 1) {
        subPackage = array[0];
        assert(subPackage === null, "Not a null for content package?");
        treeitem = itemTemplate.cloneNode(true);
        treeitem.ownerTree = ownerTree;
        cell = treeitem.firstChild.firstChild;
        for (i = 0; i < cols.length; i++)
        {
          if (cols[i] == "leaf") {
            cell.setAttribute("label", "content");
          }
          cell = cell.nextSibling;
        }
        treechildren.appendChild(treeitem);
        baseURI = gDocumentPack.getChromeBaseURI(aPackageName,
                                                 PACKAGE_TYPE_CONTENT,
                                                 subPackage);
        assert(baseURI, "No base URI?");
        this.addBaseURIDir(treeitem, baseURI);
      }

      // Locales.
      aCount = {};
      array = gDocumentPack.getSubPackageNames(aPackageName,
                                               PACKAGE_TYPE_LOCALE,
                                               aCount);
      for each (subPackage in array) {
        treeitem = itemTemplate.cloneNode(true);
        treeitem.ownerTree = ownerTree;
        cell = treeitem.firstChild.firstChild;
        for (i = 0; i < cols.length; i++)
        {
          if (cols[i] == "leaf") {
            cell.setAttribute("label", "locale");
            cell.nextSibling.setAttribute("label", subPackage);
          }
          cell = cell.nextSibling;
        }
        treechildren.appendChild(treeitem);
        baseURI = gDocumentPack.getChromeBaseURI(aPackageName,
                                                 PACKAGE_TYPE_LOCALE,
                                                 subPackage);
        assert(baseURI, "No base URI?");
        this.addBaseURIDir(treeitem, baseURI);
      }

      // Skins.
      aCount = {};
      array = gDocumentPack.getSubPackageNames(aPackageName,
                                               PACKAGE_TYPE_SKIN,
                                               aCount);
      for each (subPackage in array) {
        treeitem = itemTemplate.cloneNode(true);
        treeitem.ownerTree = ownerTree;
        cell = treeitem.firstChild.firstChild;
        for (i = 0; i < cols.length; i++)
        {
          if (cols[i] == "leaf") {
            cell.setAttribute("label", "skin");
            cell.nextSibling.setAttribute("label", subPackage);
          }
          cell = cell.nextSibling;
        }
        treechildren.appendChild(treeitem);
        baseURI = gDocumentPack.getChromeBaseURI(aPackageName,
                                                 PACKAGE_TYPE_SKIN,
                                                 subPackage);
        assert(baseURI, "No base URI?");
        this.addBaseURIDir(treeitem, baseURI);
      }
    }
  }),
  
  /**
   * Add a chrome package to the tree.
   *
   * @param aTree        The tree receiving the package.
   * @param aPackageName The name of the package.
   */
  addChromePackage: getContractFunction({
    precondition: function addChromePackage_pre(aTree, aPackageName) {
      assert(aTree.namespaceURI == XUL_NS, "Not a XUL tree?");
      assert(aTree.localName == "tree", "Not a XUL tree?");
      var itemTemplate = aTree.getUserData("itemTemplate");
      assert(itemTemplate, "No tree item template?");
      assert(typeof aPackageName == "string", "What is aPackageName?");
      assert(aPackageName, "Empty package name?");
    },
    
    body: function addChromePackage(aTree, aPackageName) {
      var treechildren = aTree.lastChild;
      var treeitem = aTree.getUserData("itemTemplate").cloneNode(true);
      treeitem.ownerTree = aTree;
      var cell = treeitem.firstChild.firstChild;

      var cols = aTree.propertyCols;
      for (var i = 0; i < cols.length; i++)
      {
        if (cols[i] == "leaf") {
          cell.setAttribute("label", aPackageName);
        }
        cell = cell.nextSibling;
      }
      treeitem.setAttribute("container", "true");
      treeitem.setAttribute("open", "true");
      treechildren.appendChild(treeitem);
      this.addPackageTypes(treeitem, aPackageName);
    }
  }),

  /**
   * Add tree items where the tree needs them.
   *
   * @param evt DOM event targeted at the tree item being opened.
   */
  onOpenRow: function onOpenRow(evt) {
    if (evt.attrName != "open")
      return;

    if (evt.newValue != "true")
      return;

    var treeitem = evt.target;
    if (treeitem.lastChild.firstChild) {
      return;
    }

    var fileObject = treeitem.getUserData("referenceFile");
    assert(fileObject, "No directory object?");
    fileObject = fileObject.wrappedJSObject;
    dump("fileObject: " + fileObject + "\n");
    assert(fileObject.isDir(), "Not a directory?");
    var treeitemTemplate = evt.currentTarget.getUserData("itemTemplate");

    var contents = fileObject.readDir();
    contents.sort();

    for (var i = 0; i < contents.length; i++)
    {
      var child = contents[i];
      dirTreeViewMaker.addToChildren(treeitem.lastChild, child, treeitemTemplate, null, 0);
    }
  },
  
  selectFiles: function selectFiles(evt) {
    var tree = evt.currentTarget;
    var selection = tree.view.selection;
    var rangeCount = selection.getRangeCount();
    const nsICategoryManager = Components.interfaces.nsICategoryManager;
    const catMan =  Components.classes["@mozilla.org/categorymanager;1"]
                              .getService(nsICategoryManager);
    const category = "Verbosio document wrappers by file type";
    gSelectedFiles = [];
    for (var i = 0; i < rangeCount; i++) {
      var min = {};
      var max = {};
      var range = selection.getRangeAt(i, min, max);
      for (var j = min.value; j <= max.value; j++) {
        var item = tree.view.getItemAtIndex(j);
        var fileObj = item.getUserData("referenceFile");
        if (!fileObj) {
          // Sometimes we have a menu item without a selected file.
          continue;
        }
        fileObj = fileObj.wrappedJSObject;
        assert(fileObj, "No reference file?");

        if (!fileObj.isFile()) {
          continue;
        }
        var ext = fileObj.ext;
        if (ext.charAt(0) == ".") {
          ext = ext.substr(1);
        }
        var catFound = false;
        try {
          catFound = Boolean(catMan.getCategoryEntry(category, ext));
          if (catFound) {
            gSelectedFiles[gSelectedFiles.length] = fileObj.path;
          }
        }
        catch (e) {
        }
        dump("catFound (" + ext + "): " + catFound + "\n");
      }
    }
    baseGlobalController.updateCommands();
  },
  
  onDoubleClick: function onDoubleClick(evt) {
    var tree = evt.currentTarget;
    var item = tree.getElementsByAttribute("defaultcommand", "true")[0];
    var command = document.getElementById(item.getAttribute("command"));
    
    var cmdEvent = document.createEvent('Events');
    cmdEvent.initEvent("command", true, true);
    command.dispatchEvent(cmdEvent);
  },

  addListeners: function addListeners(aTree)
  {
    aTree.addEventListener("DOMAttrModified", this.onOpenRow, true);
    aTree.addEventListener("select", this.selectFiles, true);
    aTree.addEventListener("dblclick", this.onDoubleClick, true);
  },

  removeListeners: function removeListeners(aTree)
  {
    aTree.removeEventListener("DOMAttrModified", this.onOpenRow, true);
  },

  /**
   * Refresh a tree with a given template.
   *
   * @param aTree Tree to generate chrome registry for.
   */
  refreshTree: function refreshTree(aTree)
  {
    // Remove event listeners.
    this.removeListeners(aTree);

    // Clear all child items
    var treechildren = aTree.lastChild;
    while (treechildren.localName != "treechildren")
      treechildren = treechildren.previousSibling;
    while (treechildren.firstChild)
    {
      var item = treechildren.firstChild;
      treechildren.removeChild(item);
    }

    // Determine if we need to create a treeitem template.
    var addCol = !(aTree.getUserData("itemTemplate"));
    if (addCol)
    {
      var template = document.createElementNS(XUL_NS, "treeitem");
      var treerow = document.createElementNS(XUL_NS, "treerow");
      template.appendChild(treerow);
      var templateChildren = document.createElementNS(XUL_NS, "treechildren");
      template.appendChild(templateChildren);
      aTree.setUserData("itemTemplate", template, null);
      template.ownerTree = aTree;
    }

    // Determine columns to insert node properties in.
    var cols = [];
    var currentCol = aTree.getElementsByTagNameNS(XUL_NS, "treecol")[0];
    while (currentCol)
    {
      cols[cols.length] = currentCol.getAttribute("propertyname");
      if (addCol)
      {
        var treecell = document.createElementNS(XUL_NS, "treecell");
        treerow.appendChild(treecell);
      }
      currentCol = currentCol.nextSibling;
    }

    if (typeof aTree.propertyCols == "undefined")
      aTree.propertyCols = cols;

    // Generate the tree's descendants.
    var packages = gDocumentPack.getChromePackageNames({});
    packages.sort();
    for (var i = 0; i < packages.length; i++) {
      this.addChromePackage(aTree, packages[i]);
    }

    // Add event listeners.
    this.addListeners(aTree);
  }
}

/**
 * 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 chrome-contents.js's notifyEvent with type " + aType + "\n");

  switch (aType) {
    case "select":
    case "focus":
    case "blur":
    case "mutation":
    case "repaint":
      break;

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

function getViewer() {
  assert(typeof JS_DIR_LOADED == "boolean" && JS_DIR_LOADED, "Where's Dir?");
  assert(typeof Dir == "function", "Where's the Dir function?");
  dump("Getting viewer for chrome directory contents!\n");
  
  const xeIDocumentPack = Components.interfaces.xeIDocumentPack;
  assert(gDocumentPack instanceof xeIDocumentPack, "No document pack?");

  var viewer = document.getElementByAttrKey(uuid, "chrome-contents");
  viewer = viewer.cloneNode(true);

  var tree = viewer.getElementsByAttribute("localid", "chrome-tree")[0];

  dirTreeViewMaker.refreshTree(tree);
  
  viewer.notifyEvent = notifyEvent;

  return viewer;
}
