/* 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/. */

var resultObserver = {
  insertedNode: null,
  nodeInserted(parent, node) {
    this.insertedNode = node;
  },
  removedNode: null,
  nodeRemoved(parent, node) {
    this.removedNode = node;
  },

  newTitle: "",
  nodeChangedByTitle: null,
  nodeTitleChanged(node, newTitle) {
    this.nodeChangedByTitle = node;
    this.newTitle = newTitle;
  },

  newAccessCount: 0,
  newTime: 0,
  nodeChangedByHistoryDetails: null,
  nodeHistoryDetailsChanged(node) {
    this.nodeChangedByHistoryDetails = node;
    this.newTime = node.time;
    this.newAccessCount = node.accessCount;
  },

  movedNode: null,
  nodeMoved(node) {
    this.movedNode = node;
  },
  openedContainer: null,
  closedContainer: null,
  containerStateChanged(aNode, aOldState, aNewState) {
    if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
      this.openedContainer = aNode;
    } else if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) {
      this.closedContainer = aNode;
    }
  },
  invalidatedContainer: null,
  invalidateContainer(node) {
    this.invalidatedContainer = node;
  },
  sortingMode: null,
  sortingChanged(sortingMode) {
    this.sortingMode = sortingMode;
  },
  inBatchMode: false,
  batchingCallCount: 0,
  batching(aToggleMode) {
    Assert.notEqual(this.inBatchMode, aToggleMode);
    this.inBatchMode = aToggleMode;
    this.batchingCallCount++;
  },
  result: null,
  reset() {
    this.insertedNode = null;
    this.removedNode = null;
    this.nodeChangedByTitle = null;
    this.nodeChangedByHistoryDetails = null;
    this.replacedNode = null;
    this.movedNode = null;
    this.openedContainer = null;
    this.closedContainer = null;
    this.invalidatedContainer = null;
    this.sortingMode = null;
    this.inBatchMode = false;
    this.batchingCallCount = 0;
  },
};

var testURI = uri("http://mozilla.com");

add_task(async function check_history_query() {
  let options = PlacesUtils.history.getNewQueryOptions();
  options.sortingMode = options.SORT_BY_DATE_DESCENDING;
  options.resultType = options.RESULTS_AS_VISIT;
  let query = PlacesUtils.history.getNewQuery();
  let result = PlacesUtils.history.executeQuery(query, options);
  result.addObserver(resultObserver);
  let root = result.root;
  root.containerOpen = true;

  Assert.notEqual(resultObserver.openedContainer, null);

  // nsINavHistoryResultObserver.nodeInserted
  // add a visit
  await PlacesTestUtils.addVisits(testURI);
  Assert.equal(testURI.spec, resultObserver.insertedNode.uri);

  // nsINavHistoryResultObserver.nodeHistoryDetailsChanged
  // adding a visit causes nodeHistoryDetailsChanged for the folder
  Assert.equal(root.uri, resultObserver.nodeChangedByHistoryDetails.uri);

  // nsINavHistoryResultObserver.itemTitleChanged for a leaf node
  await PlacesTestUtils.addVisits({ uri: testURI, title: "baz" });
  Assert.equal(resultObserver.nodeChangedByTitle.title, "baz");

  // nsINavHistoryResultObserver.nodeRemoved
  let removedURI = uri("http://google.com");
  await PlacesTestUtils.addVisits(removedURI);
  await PlacesUtils.history.remove(removedURI);
  Assert.equal(removedURI.spec, resultObserver.removedNode.uri);

  // nsINavHistoryResultObserver.invalidateContainer
  await PlacesUtils.history.removeByFilter({ host: "mozilla.com" });
  // This test is disabled for bug 1089691. It is failing bcause the new API
  // doesn't send batching notifications and thus the result doesn't invalidate
  // the whole container.
  // Assert.equal(root.uri, resultObserver.invalidatedContainer.uri);

  // nsINavHistoryResultObserver.sortingChanged
  resultObserver.invalidatedContainer = null;
  result.sortingMode = options.SORT_BY_TITLE_ASCENDING;
  Assert.equal(resultObserver.sortingMode, options.SORT_BY_TITLE_ASCENDING);
  Assert.equal(resultObserver.invalidatedContainer, result.root);

  // nsINavHistoryResultObserver.invalidateContainer
  await PlacesUtils.history.clear();
  Assert.equal(root.uri, resultObserver.invalidatedContainer.uri);

  root.containerOpen = false;
  Assert.equal(resultObserver.closedContainer, resultObserver.openedContainer);
  result.removeObserver(resultObserver);
  resultObserver.reset();
  await PlacesTestUtils.promiseAsyncUpdates();
});

add_task(async function check_bookmarks_query() {
  let options = PlacesUtils.history.getNewQueryOptions();
  let query = PlacesUtils.history.getNewQuery();
  query.setParents([PlacesUtils.bookmarks.menuGuid]);
  let result = PlacesUtils.history.executeQuery(query, options);
  result.addObserver(resultObserver);
  let root = result.root;
  root.containerOpen = true;

  Assert.notEqual(resultObserver.openedContainer, null);

  // nsINavHistoryResultObserver.nodeInserted
  // add a bookmark
  let testBookmark = await PlacesUtils.bookmarks.insert({
    parentGuid: PlacesUtils.bookmarks.menuGuid,
    url: testURI,
    title: "foo",
  });
  Assert.equal("foo", resultObserver.insertedNode.title);
  Assert.equal(testURI.spec, resultObserver.insertedNode.uri);

  // nsINavHistoryResultObserver.nodeHistoryDetailsChanged
  // adding a visit causes nodeHistoryDetailsChanged for the folder
  Assert.equal(root.uri, resultObserver.nodeChangedByHistoryDetails.uri);

  // nsINavHistoryResultObserver.nodeTitleChanged for a leaf node
  await PlacesUtils.bookmarks.update({
    guid: testBookmark.guid,
    title: "baz",
  });
  Assert.equal(resultObserver.nodeChangedByTitle.title, "baz");
  Assert.equal(resultObserver.newTitle, "baz");

  let testBookmark2 = await PlacesUtils.bookmarks.insert({
    parentGuid: PlacesUtils.bookmarks.menuGuid,
    url: "http://google.com",
    title: "foo",
  });

  await PlacesUtils.bookmarks.update({
    guid: testBookmark2.guid,
    index: 0,
    parentGuid: PlacesUtils.bookmarks.menuGuid,
  });
  Assert.equal(resultObserver.movedNode.bookmarkGuid, testBookmark2.guid);

  // nsINavHistoryResultObserver.nodeRemoved
  await PlacesUtils.bookmarks.remove(testBookmark2.guid);
  Assert.equal(testBookmark2.guid, resultObserver.removedNode.bookmarkGuid);

  // XXX nsINavHistoryResultObserver.invalidateContainer

  // nsINavHistoryResultObserver.sortingChanged
  resultObserver.invalidatedContainer = null;
  result.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING;
  Assert.equal(
    resultObserver.sortingMode,
    Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING
  );
  Assert.equal(resultObserver.invalidatedContainer, result.root);

  root.containerOpen = false;
  Assert.equal(resultObserver.closedContainer, resultObserver.openedContainer);
  result.removeObserver(resultObserver);
  resultObserver.reset();
  await PlacesTestUtils.promiseAsyncUpdates();
});

add_task(async function check_mixed_query() {
  let options = PlacesUtils.history.getNewQueryOptions();
  let query = PlacesUtils.history.getNewQuery();
  let result = PlacesUtils.history.executeQuery(query, options);
  result.addObserver(resultObserver);
  let root = result.root;
  root.containerOpen = true;

  Assert.notEqual(resultObserver.openedContainer, null);

  root.containerOpen = false;
  Assert.equal(resultObserver.closedContainer, resultObserver.openedContainer);
  result.removeObserver(resultObserver);
  resultObserver.reset();
  await PlacesTestUtils.promiseAsyncUpdates();
});

add_task(async function check_a_batch_process() {
  const options = PlacesUtils.history.getNewQueryOptions();
  const query = PlacesUtils.history.getNewQuery();
  const result = PlacesUtils.history.executeQuery(query, options);
  result.addObserver(resultObserver);

  info("Check initial state");
  Assert.equal(resultObserver.inBatchMode, false);
  Assert.equal(resultObserver.batchingCallCount, 0);

  info("Check whether batching is called when call onBeginUpdateBatch");
  result.onBeginUpdateBatch();
  Assert.equal(resultObserver.inBatchMode, true);
  Assert.equal(resultObserver.batchingCallCount, 1);

  info("Check whether batching is called when call onEndUpdateBatch");
  result.onEndUpdateBatch();
  Assert.equal(resultObserver.inBatchMode, false);
  Assert.equal(resultObserver.batchingCallCount, 2);

  result.removeObserver(resultObserver);
  resultObserver.reset();
  await PlacesTestUtils.promiseAsyncUpdates();
});

add_task(async function check_multi_batch_processes() {
  const options = PlacesUtils.history.getNewQueryOptions();
  const query = PlacesUtils.history.getNewQuery();
  const result = PlacesUtils.history.executeQuery(query, options);
  result.addObserver(resultObserver);

  info("Check initial state");
  Assert.equal(resultObserver.inBatchMode, false);
  Assert.equal(resultObserver.batchingCallCount, 0);

  info("Check whether batching is called when calling onBeginUpdateBatch");
  result.onBeginUpdateBatch();
  Assert.equal(resultObserver.inBatchMode, true);
  Assert.equal(resultObserver.batchingCallCount, 1);

  info(
    "Check whether batching is not called when calling onBeginUpdateBatch again"
  );
  result.onBeginUpdateBatch();
  Assert.equal(resultObserver.inBatchMode, true);
  Assert.equal(resultObserver.batchingCallCount, 1);

  info(
    "Check whether batching is not called until calling onEndUpdateBatch the same number times that onBeginUpdateBatch is called"
  );
  result.onEndUpdateBatch();
  Assert.equal(resultObserver.inBatchMode, true);
  Assert.equal(resultObserver.batchingCallCount, 1);

  info(
    "Check whether batching is called when calling onEndUpdateBatch the same number times"
  );
  result.onEndUpdateBatch();
  Assert.equal(resultObserver.inBatchMode, false);
  Assert.equal(resultObserver.batchingCallCount, 2);

  result.removeObserver(resultObserver);
  resultObserver.reset();
  await PlacesTestUtils.promiseAsyncUpdates();
});
