/* ***** 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 Directory 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);
    /* XXX ajvincent This is not scalable; we need some other way to tie tree
       items to nodes (for example, when a node has more than one tree item,
       such as for a XML document wrapper and a XML fragment wrapper.)
     */
    aFileObject.treeitemNode = treeitem;
    var cell = treeitem.firstChild.firstChild;

    var cols = treeitemTemplate.ownerTree.propertyCols;
    for (var i = 0; i < cols.length; i++)
    {
      if (!cols[i])
        continue;
      if (!aFileObject[cols[i]])
        continue;
      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);
  },

  /**
   * 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");
        assert(fileObj, "No reference file?");
        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 and a new root node.
   *
   * @param aTree         
   * @param aRootDirObj
   */
  refreshTree: function refreshTree(aTree, aRootDirObj)
  {
    // 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;
      var refNode = item.getUserData("referenceNode");
      if (refNode)
      {
        this.removeListeners(refNode, aTree);
      }
      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.
    this.addToChildren(treechildren, aRootDirObj, template, null, 2);
    assert(aRootDirObj.treeitemNode, "No tree item node?");

    // 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 dir-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 directory contents!\n");
  
  const xeIDocumentPack = Components.interfaces.xeIDocumentPack;
  assert(gDocumentPack instanceof xeIDocumentPack, "No document pack?");

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

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

  var path = gDocumentPack.initialAppPath;
  var slash = (path.indexOf("/") == -1) ? "\\" : "/";
  var dirPath = path.substr(0, path.lastIndexOf(slash));
  var dirObj = new Dir(dirPath);
  
  dirTreeViewMaker.refreshTree(tree, dirObj);
  
  viewer.notifyEvent = notifyEvent;

  return viewer;
}
