// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// require: cr.js
// require: cr/ui.js
// require: cr/ui/tree.js

(function() {
  /**
   * A helper function to determine if a node is the root of its type.
   *
   * @param {!Object} node The node to check.
   */
  var isTypeRootNode = function(node) {
    return node.PARENT_ID == 'r' && node.UNIQUE_SERVER_TAG != '';
  };

  /**
   * A helper function to determine if a node is a child of the given parent.
   *
   * @param {!Object} parent node.
   * @param {!Object} node The node to check.
   */
  var isChildOf = function(parentNode, node) {
    if (node.PARENT_ID != '') {
      return node.PARENT_ID == parentNode.ID;
    }
    else {
      return node.modelType == parentNode.modelType;
    }
  };

  /**
   * A helper function to sort sync nodes.
   *
   * Sorts by position index if possible, falls back to sorting by name, and
   * finally sorting by METAHANDLE.
   *
   * If this proves to be slow and expensive, we should experiment with moving
   * this functionality to C++ instead.
   */
  var nodeComparator = function(nodeA, nodeB) {
    if (nodeA.hasOwnProperty('positionIndex') &&
        nodeB.hasOwnProperty('positionIndex')) {
      return nodeA.positionIndex - nodeB.positionIndex;
    } else if (nodeA.NON_UNIQUE_NAME != nodeB.NON_UNIQUE_NAME) {
      return nodeA.NON_UNIQUE_NAME.localeCompare(nodeB.NON_UNIQUE_NAME);
    } else {
      return nodeA.METAHANDLE - nodeB.METAHANDLE;
    }
  };

  /**
   * Updates the node detail view with the details for the given node.
   * @param {!Object} node The struct representing the node we want to display.
   */
  function updateNodeDetailView(node) {
    var nodeDetailsView = $('node-details');
    nodeDetailsView.hidden = false;
    jstProcess(new JsEvalContext(node.entry_), nodeDetailsView);
  }

  /**
   * Updates the 'Last refresh time' display.
   * @param {string} The text to display.
   */
  function setLastRefreshTime(str) {
    $('node-browser-refresh-time').textContent = str;
  }

  /**
   * Creates a new sync node tree item.
   *
   * @constructor
   * @param {!Object} node The nodeDetails object for the node as returned by
   *     chrome.sync.getAllNodes().
   * @extends {cr.ui.TreeItem}
   */
  var SyncNodeTreeItem = function(node) {
    var treeItem = new cr.ui.TreeItem();
    treeItem.__proto__ = SyncNodeTreeItem.prototype;

    treeItem.entry_ = node;
    treeItem.label = node.NON_UNIQUE_NAME;
    if (node.IS_DIR) {
      treeItem.mayHaveChildren_ = true;

      // Load children on expand.
      treeItem.expanded_ = false;
      treeItem.addEventListener('expand',
                                treeItem.handleExpand_.bind(treeItem));
    } else {
      treeItem.classList.add('leaf');
    }
    return treeItem;
  };

  SyncNodeTreeItem.prototype = {
    __proto__: cr.ui.TreeItem.prototype,

    /**
     * Finds the children of this node and appends them to the tree.
     */
    handleExpand_: function(event) {
      var treeItem = this;

      if (treeItem.expanded_) {
        return;
      }
      treeItem.expanded_ = true;

      var children = treeItem.tree.allNodes.filter(
          isChildOf.bind(undefined, treeItem.entry_));
      children.sort(nodeComparator);

      children.forEach(function(node) {
        treeItem.add(new SyncNodeTreeItem(node));
      });
    },
  };

  /**
   * Creates a new sync node tree.  Technically, it's a forest since it each
   * type has its own root node for its own tree, but it still looks and acts
   * mostly like a tree.
   *
   * @param {Object=} opt_propertyBag Optional properties.
   * @constructor
   * @extends {cr.ui.Tree}
   */
  var SyncNodeTree = cr.ui.define('tree');

  SyncNodeTree.prototype = {
    __proto__: cr.ui.Tree.prototype,

    decorate: function() {
      cr.ui.Tree.prototype.decorate.call(this);
      this.addEventListener('change', this.handleChange_.bind(this));
      this.allNodes = [];
    },

    populate: function(nodes) {
      var tree = this;

      // We store the full set of nodes in the SyncNodeTree object.
      tree.allNodes = nodes;

      var roots = tree.allNodes.filter(isTypeRootNode);
      roots.sort(nodeComparator);

      roots.forEach(function(typeRoot) {
        tree.add(new SyncNodeTreeItem(typeRoot));
      });
    },

    handleChange_: function(event) {
      if (this.selectedItem) {
        updateNodeDetailView(this.selectedItem);
      }
    }
  };

  /**
   * Clears any existing UI state.  Useful prior to a refresh.
   */
  function clear() {
    var treeContainer = $('sync-node-tree-container');
    while (treeContainer.firstChild) {
      treeContainer.removeChild(treeContainer.firstChild);
    }

    var nodeDetailsView = $('node-details');
    nodeDetailsView.hidden = true;
  }

  /**
   * Fetch the latest set of nodes and refresh the UI.
   */
  function refresh() {
    $('node-browser-refresh-button').disabled = true;

    clear();
    setLastRefreshTime('In progress since ' + (new Date()).toLocaleString());

    chrome.sync.getAllNodes(function(nodeMap) {
      // Put all nodes into one big list that ignores the type.
      var nodes = nodeMap.
          map(function(x) { return x.nodes; }).
          reduce(function(a, b) { return a.concat(b); });

      var treeContainer = $('sync-node-tree-container');
      var tree = document.createElement('tree');
      tree.setAttribute('id', 'sync-node-tree');
      tree.setAttribute('icon-visibility', 'parent');
      treeContainer.appendChild(tree);

      cr.ui.decorate(tree, SyncNodeTree);
      tree.populate(nodes);

      setLastRefreshTime((new Date()).toLocaleString());
      $('node-browser-refresh-button').disabled = false;
    });
  }

  document.addEventListener('DOMContentLoaded', function(e) {
    $('node-browser-refresh-button').addEventListener('click', refresh);
    cr.ui.decorate('#sync-node-splitter', cr.ui.Splitter);

    // Automatically trigger a refresh the first time this tab is selected.
    $('sync-browser-tab').addEventListener('selectedChange', function f(e) {
      if (this.selected) {
        $('sync-browser-tab').removeEventListener('selectedChange', f);
        refresh();
      }
    });
  });

})();
