/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Tests that we build a working leftpane in various corruption situations.
 */

// Used to store the original leftPaneFolderId getter.
var gLeftPaneFolderIdGetter;
var gAllBookmarksFolderIdGetter;
// Used to store the original left Pane status as a JSON string.
var gReferenceJSON;
var gLeftPaneFolderId;
// Third party annotated folder.
var gFolderId;

// Corruption cases.
var gTests = [

  function test1() {
    print("1. Do nothing, checks test calibration.");
  },

  function test2() {
    print("2. Delete the left pane folder.");
    PlacesUtils.bookmarks.removeItem(gLeftPaneFolderId);
  },

  function test3() {
    print("3. Delete a child of the left pane folder.");
    let id = PlacesUtils.bookmarks.getIdForItemAt(gLeftPaneFolderId, 0);
    PlacesUtils.bookmarks.removeItem(id);
  },

  function test4() {
    print("4. Delete AllBookmarks.");
    PlacesUtils.bookmarks.removeItem(PlacesUIUtils.allBookmarksFolderId);
  },

  function test5() {
    print("5. Create a duplicated left pane folder.");
    let id = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
                                                "PlacesRoot",
                                                PlacesUtils.bookmarks.DEFAULT_INDEX);
    PlacesUtils.annotations.setItemAnnotation(id, ORGANIZER_FOLDER_ANNO,
                                              "PlacesRoot", 0,
                                              PlacesUtils.annotations.EXPIRE_NEVER);
  },

  function test6() {
    print("6. Create a duplicated left pane query.");
    let id = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
                                                "AllBookmarks",
                                                PlacesUtils.bookmarks.DEFAULT_INDEX);
    PlacesUtils.annotations.setItemAnnotation(id, ORGANIZER_QUERY_ANNO,
                                              "AllBookmarks", 0,
                                              PlacesUtils.annotations.EXPIRE_NEVER);
  },

  function test7() {
    print("7. Remove the left pane folder annotation.");
    PlacesUtils.annotations.removeItemAnnotation(gLeftPaneFolderId,
                                                 ORGANIZER_FOLDER_ANNO);
  },

  function test8() {
    print("8. Remove a left pane query annotation.");
    PlacesUtils.annotations.removeItemAnnotation(PlacesUIUtils.allBookmarksFolderId,
                                                 ORGANIZER_QUERY_ANNO);
  },

  function test9() {
    print("9. Remove a child of AllBookmarks.");
    let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUIUtils.allBookmarksFolderId, 0);
    PlacesUtils.bookmarks.removeItem(id);
  },

];

function run_test() {
  // We want empty roots.
  remove_all_bookmarks();

  // Sanity check.
  Assert.ok(!!PlacesUIUtils);

  // Check getters.
  gLeftPaneFolderIdGetter = PlacesUIUtils.__lookupGetter__("leftPaneFolderId");
  Assert.equal(typeof(gLeftPaneFolderIdGetter), "function");
  gAllBookmarksFolderIdGetter = PlacesUIUtils.__lookupGetter__("allBookmarksFolderId");
  Assert.equal(typeof(gAllBookmarksFolderIdGetter), "function");

  // Add a third party bogus annotated item.  Should not be removed.
  gFolderId = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
                                                 "test",
                                                 PlacesUtils.bookmarks.DEFAULT_INDEX);
  PlacesUtils.annotations.setItemAnnotation(gFolderId, ORGANIZER_QUERY_ANNO,
                                            "test", 0,
                                            PlacesUtils.annotations.EXPIRE_NEVER);

  // Create the left pane, and store its current status, it will be used
  // as reference value.
  gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
  gReferenceJSON = folderToJSON(gLeftPaneFolderId);

  // Kick-off tests.
  do_test_pending();
  do_timeout(0, run_next_test);
}

function run_next_test() {
  if (gTests.length) {
    // Create corruption.
    let test = gTests.shift();
    test();
    // Regenerate getters.
    PlacesUIUtils.__defineGetter__("leftPaneFolderId", gLeftPaneFolderIdGetter);
    gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
    PlacesUIUtils.__defineGetter__("allBookmarksFolderId", gAllBookmarksFolderIdGetter);
    // Check the new left pane folder.
    let leftPaneJSON = folderToJSON(gLeftPaneFolderId);
    Assert.ok(compareJSON(gReferenceJSON, leftPaneJSON));
    Assert.equal(PlacesUtils.bookmarks.getItemTitle(gFolderId), "test");
    // Go to next test.
    do_timeout(0, run_next_test);
  }
  else {
    // All tests finished.
    remove_all_bookmarks();
    do_test_finished();
  }
}

/**
 * Convert a folder item id to a JSON representation of it and its contents.
 */
function folderToJSON(aItemId) {
  let query = PlacesUtils.history.getNewQuery();
  query.setFolders([aItemId], 1);
  let options = PlacesUtils.history.getNewQueryOptions();
  options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
  let root = PlacesUtils.history.executeQuery(query, options).root;
  let writer = {
    value: "",
    write: function PU_wrapNode__write(aStr, aLen) {
      this.value += aStr;
    }
  };
  PlacesUtils.serializeNodeAsJSONToOutputStream(root, writer, false, false);
  Assert.ok(writer.value.length > 0);
  return writer.value;
}

/**
 * Compare the JSON representation of 2 nodes, skipping everchanging properties
 * like dates.
 */
function compareJSON(aNodeJSON_1, aNodeJSON_2) {
  let node1 = JSON.parse(aNodeJSON_1);
  let node2 = JSON.parse(aNodeJSON_2);

  // List of properties we should not compare (expected to be different).
  const SKIP_PROPS = ["dateAdded", "lastModified", "id"];

  function compareObjects(obj1, obj2) {
    function count(o) { var n = 0; for (let p in o) n++; return n; }
    Assert.equal(count(obj1), count(obj2));
    for (let prop in obj1) {
      // Skip everchanging values.
      if (SKIP_PROPS.indexOf(prop) != -1)
        continue;
      // Skip undefined objects, otherwise we hang on them.
      if (!obj1[prop])
        continue;
      if (typeof(obj1[prop]) == "object")
        return compareObjects(obj1[prop], obj2[prop]);
      if (obj1[prop] !== obj2[prop]) {
        print(prop + ": " + obj1[prop] + "!=" + obj2[prop]);
        return false;
      }
    }
    return true;
  }

  return compareObjects(node1, node2);
}
