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

let nowObj = new Date();

/**
 * Normalizes a Date to midnight.
 *
 * @param {Date} inputDate
 * @returns {Date}
 *   The normalized date.
 */
function toMidnight(inputDate) {
  let date = new Date(inputDate);
  date.setHours(0);
  date.setMinutes(0);
  date.setSeconds(0);
  date.setMilliseconds(0);
  return date;
}

/**
 * Adds a test URI visit to the database.
 *
 * @param {nsIURI} aURI
 *   The URI to add a visit for.
 * @param {Date} aTime
 *   Reference "now" time.
 * @param {number} aDayOffset
 *   The number of days to add, pass a negative value to subtract them.
 */
async function addNormalizedVisit(aURI, aTime, aDayOffset) {
  let dateObj = toMidnight(aTime);
  // Days where DST changes should be taken into account.
  let previousDateObj = new Date(dateObj.getTime() + aDayOffset * 86400000);
  let DSTCorrection =
    (dateObj.getTimezoneOffset() - previousDateObj.getTimezoneOffset()) *
    60 *
    1000;
  // Substract aDayOffset
  let PRTimeWithOffset = (previousDateObj.getTime() - DSTCorrection) * 1000;
  info(
    "Adding visit to " +
      aURI.spec +
      " at " +
      PlacesUtils.toDate(PRTimeWithOffset)
  );
  await PlacesTestUtils.addVisits({
    uri: aURI,
    visitDate: PRTimeWithOffset,
  });
}

function openRootForResultType(resultType) {
  let options = PlacesUtils.history.getNewQueryOptions();
  options.resultType = resultType;
  let query = PlacesUtils.history.getNewQuery();
  let result = PlacesUtils.history.executeQuery(query, options);
  let root = result.root;
  root.containerOpen = true;
  return result;
}

function daysForMonthsAgo(months) {
  let oldTime = toMidnight(new Date());
  // Set day before month, otherwise we could try to calculate 30 February, or
  // other nonexistent days.
  oldTime.setDate(1);
  oldTime.setMonth(nowObj.getMonth() - months);
  // Stay larger for eventual timezone issues, add 2 days.
  return parseInt((nowObj - oldTime) / (1000 * 60 * 60 * 24)) + 2;
}

// This test relies on en-US locale
// Offset is number of days
let containers = [
  { label: "Today", offset: 0, visible: true },
  { label: "Yesterday", offset: -1, visible: true },
  { label: "Last 7 days", offset: -2, visible: true },
  { label: "This month", offset: -8, visible: nowObj.getDate() > 8 },
  { label: "", offset: -daysForMonthsAgo(0), visible: true },
  { label: "", offset: -daysForMonthsAgo(1), visible: true },
  { label: "", offset: -daysForMonthsAgo(2), visible: true },
  { label: "", offset: -daysForMonthsAgo(3), visible: true },
  { label: "", offset: -daysForMonthsAgo(4), visible: true },
  { label: "Older than 6 months", offset: -daysForMonthsAgo(5), visible: true },
];

let visibleContainers = containers.filter(container => container.visible);

/**
 * Asynchronous task that fills history and checks containers' labels.
 */
add_task(async function task_fill_history() {
  info("*** TEST Fill History");
  // We can't use "now" because our hardcoded offsets would be invalid for some
  // date.  So we hardcode a date.
  for (let i = 0; i < containers.length; i++) {
    let container = containers[i];
    let testURI = uri("http://mirror" + i + ".mozilla.com/b");
    await addNormalizedVisit(testURI, nowObj, container.offset);
    testURI = uri("http://mirror" + i + ".mozilla.com/a");
    await addNormalizedVisit(testURI, nowObj, container.offset);
    testURI = uri("http://mirror" + i + ".google.com/b");
    await addNormalizedVisit(testURI, nowObj, container.offset);
    testURI = uri("http://mirror" + i + ".google.com/a");
    await addNormalizedVisit(testURI, nowObj, container.offset);
    // Bug 485703 - Hide date containers not containing additional entries
    //              compared to previous ones.
    // Check after every new container is added.
    check_visit(container.offset);
  }

  let root = openRootForResultType(
    Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY
  ).root;
  let cc = root.childCount;
  info("Found containers:");
  let previousLabels = [];
  for (let i = 0; i < cc; i++) {
    let container = visibleContainers[i];
    let node = root.getChild(i);
    info(node.title);
    if (container.label) {
      Assert.equal(node.title, container.label);
    }
    // Check labels are not repeated.
    Assert.ok(!previousLabels.includes(node.title));
    previousLabels.push(node.title);
  }
  Assert.equal(cc, visibleContainers.length);
  root.containerOpen = false;
});

/**
 * Bug 485703 - Hide date containers not containing additional entries compared
 *              to previous ones.
 *
 * @param {number} aOffset
 */
function check_visit(aOffset) {
  let root = openRootForResultType(
    Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY
  ).root;

  let unexpected = [];
  switch (aOffset) {
    case 0:
      unexpected = ["Yesterday", "Last 7 days", "This month"];
      break;
    case -1:
      unexpected = ["Last 7 days", "This month"];
      break;
    case -2:
      unexpected = ["This month"];
      break;
    default:
    // Other containers are tested later.
  }

  info("Found containers:");
  let cc = root.childCount;
  for (let i = 0; i < cc; i++) {
    let node = root.getChild(i);
    info(node.title);
    Assert.ok(!unexpected.includes(node.title));
  }
  root.containerOpen = false;
}

/**
 * Queries history grouped by date and site, checking containers' labels and
 * children.
 */
add_task(async function test_RESULTS_AS_DATE_SITE_QUERY() {
  info("*** TEST RESULTS_AS_DATE_SITE_QUERY");
  let result = openRootForResultType(
    Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY
  );
  let root = result.root;

  // Check one of the days
  let dayNode = PlacesUtils.asContainer(root.getChild(0));
  dayNode.containerOpen = true;
  Assert.equal(dayNode.childCount, 2);

  // Items should be sorted by host
  let site1 = PlacesUtils.asContainer(dayNode.getChild(0));
  Assert.equal(site1.title, "mirror0.google.com");

  let site2 = PlacesUtils.asContainer(dayNode.getChild(1));
  Assert.equal(site2.title, "mirror0.mozilla.com");

  site1.containerOpen = true;
  Assert.equal(site1.childCount, 2);

  // Inside of host sites are sorted by title
  let site1visit = site1.getChild(0);
  Assert.equal(site1visit.uri, "http://mirror0.google.com/a");

  // Bug 473157: changing sorting mode should not affect the containers
  result.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING;

  // Check one of the days
  dayNode = PlacesUtils.asContainer(root.getChild(0));
  dayNode.containerOpen = true;
  Assert.equal(dayNode.childCount, 2);

  // Hosts are still sorted by title
  site1 = PlacesUtils.asContainer(dayNode.getChild(0));
  Assert.equal(site1.title, "mirror0.google.com");

  site2 = PlacesUtils.asContainer(dayNode.getChild(1));
  Assert.equal(site2.title, "mirror0.mozilla.com");

  site1.containerOpen = true;
  Assert.equal(site1.childCount, 2);

  // But URLs are now sorted by title descending
  site1visit = site1.getChild(0);
  Assert.equal(site1visit.uri, "http://mirror0.google.com/b");

  site1.containerOpen = false;
  dayNode.containerOpen = false;
  root.containerOpen = false;
});

/**
 * Queries history grouped by date, checking containers' labels and children.
 */
add_task(async function test_RESULTS_AS_DATE_QUERY() {
  info("*** TEST RESULTS_AS_DATE_QUERY");
  let result = openRootForResultType(
    Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY
  );
  let root = result.root;
  let cc = root.childCount;
  Assert.equal(cc, visibleContainers.length);
  info("Found containers:");
  for (let i = 0; i < cc; i++) {
    let container = visibleContainers[i];
    let node = root.getChild(i);
    info(node.title);
    if (container.label) {
      Assert.equal(node.title, container.label);
    }
  }

  // Check one of the days
  let dayNode = PlacesUtils.asContainer(root.getChild(0));
  dayNode.containerOpen = true;
  Assert.equal(dayNode.childCount, 4);

  // Items should be sorted by title
  let visit1 = dayNode.getChild(0);
  Assert.equal(visit1.uri, "http://mirror0.google.com/a");

  let visit2 = dayNode.getChild(3);
  Assert.equal(visit2.uri, "http://mirror0.mozilla.com/b");

  // Bug 473157: changing sorting mode should not affect the containers
  result.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING;

  // Check one of the days
  dayNode = PlacesUtils.asContainer(root.getChild(0));
  dayNode.containerOpen = true;
  Assert.equal(dayNode.childCount, 4);

  // But URLs are now sorted by title descending
  visit1 = dayNode.getChild(0);
  Assert.equal(visit1.uri, "http://mirror0.mozilla.com/b");

  visit2 = dayNode.getChild(3);
  Assert.equal(visit2.uri, "http://mirror0.google.com/a");

  dayNode.containerOpen = false;
  root.containerOpen = false;
});

/**
 * Queries history grouped by site, checking containers' labels and children.
 */
add_task(async function test_RESULTS_AS_SITE_QUERY() {
  info("*** TEST RESULTS_AS_SITE_QUERY");
  // add a bookmark with a domain not in the set of visits in the db
  let bookmark = await PlacesUtils.bookmarks.insert({
    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
    url: "http://foobar",
    title: "",
  });

  let options = PlacesUtils.history.getNewQueryOptions();
  options.resultType = options.RESULTS_AS_SITE_QUERY;
  options.sortingMode = options.SORT_BY_TITLE_ASCENDING;
  let query = PlacesUtils.history.getNewQuery();
  let result = PlacesUtils.history.executeQuery(query, options);
  let root = result.root;
  root.containerOpen = true;
  Assert.equal(root.childCount, containers.length * 2);

  // Expected results:
  //   "mirror0.google.com",
  //   "mirror0.mozilla.com",
  //   "mirror1.google.com",
  //   "mirror1.mozilla.com",
  //   "mirror2.google.com",
  //   "mirror2.mozilla.com",
  //   "mirror3.google.com",  <== We check for this site (index 6)
  //   "mirror3.mozilla.com",
  //   "mirror4.google.com",
  //   "mirror4.mozilla.com",
  //   "mirror5.google.com",
  //   "mirror5.mozilla.com",
  //   ...

  // Items should be sorted by host
  let siteNode = PlacesUtils.asContainer(root.getChild(6));
  Assert.equal(siteNode.title, "mirror3.google.com");

  siteNode.containerOpen = true;
  Assert.equal(siteNode.childCount, 2);

  // Inside of host sites are sorted by title
  let visitNode = siteNode.getChild(0);
  Assert.equal(visitNode.uri, "http://mirror3.google.com/a");

  // Bug 473157: changing sorting mode should not affect the containers
  result.sortingMode = options.SORT_BY_TITLE_DESCENDING;
  siteNode = PlacesUtils.asContainer(root.getChild(6));
  Assert.equal(siteNode.title, "mirror3.google.com");

  siteNode.containerOpen = true;
  Assert.equal(siteNode.childCount, 2);

  // But URLs are now sorted by title descending
  let visit = siteNode.getChild(0);
  Assert.equal(visit.uri, "http://mirror3.google.com/b");

  siteNode.containerOpen = false;
  root.containerOpen = false;

  // Cleanup.
  await PlacesUtils.bookmarks.remove(bookmark.guid);
});

/**
 * Checks that queries grouped by date do liveupdate correctly.
 *
 * @param {nsINavHistoryQueryOptions.ResultType} aResultType
 */
async function test_date_liveupdate(aResultType) {
  let midnight = toMidnight(nowObj);

  // TEST 1. Test that the query correctly updates when it is root.
  let root = openRootForResultType(aResultType).root;
  Assert.equal(root.childCount, visibleContainers.length);

  // Remove "Today".
  await PlacesUtils.history.removeByFilter({
    beginDate: new Date(midnight.getTime()),
    endDate: new Date(Date.now()),
  });
  Assert.equal(root.childCount, visibleContainers.length - 1);

  // Open "Last 7 days" container, this way we will have a container accepting
  // the new visit, but we should still add back "Today" container.
  let last7Days = PlacesUtils.asContainer(root.getChild(1));
  last7Days.containerOpen = true;

  // Add a visit for "Today".  This should add back the missing "Today"
  // container.
  await addNormalizedVisit(uri("http://www.mozilla.org/"), nowObj, 0);
  Assert.equal(root.childCount, visibleContainers.length);

  last7Days.containerOpen = false;
  root.containerOpen = false;

  // TEST 2. Test that the query correctly updates even if it is not root.
  let bookmark = await PlacesUtils.bookmarks.insert({
    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
    url: "place:type=" + aResultType,
    title: "",
  });

  // Query toolbar and open our query container, then check again liveupdate.
  root = PlacesUtils.getFolderContents(PlacesUtils.bookmarks.toolbarGuid).root;
  Assert.equal(root.childCount, 1);
  let dateContainer = PlacesUtils.asContainer(root.getChild(0));
  dateContainer.containerOpen = true;

  Assert.equal(dateContainer.childCount, visibleContainers.length);
  // Remove "Today".
  await PlacesUtils.history.removeByFilter({
    beginDate: new Date(midnight.getTime()),
    endDate: new Date(Date.now()),
  });
  Assert.equal(dateContainer.childCount, visibleContainers.length - 1);
  // Add a visit for "Today".
  await addNormalizedVisit(uri("http://www.mozilla.org/"), nowObj, 0);
  Assert.equal(dateContainer.childCount, visibleContainers.length);

  dateContainer.containerOpen = false;
  root.containerOpen = false;

  // Cleanup.
  await PlacesUtils.bookmarks.remove(bookmark.guid);
}

add_task(async function test_history_sidebar() {
  await test_date_liveupdate(
    Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY
  );
  await test_date_liveupdate(
    Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY
  );

  // The remaining views are
  //   RESULTS_AS_URI + SORT_BY_VISITCOUNT_DESCENDING
  //   ->  test_399266.js
  //   RESULTS_AS_URI + SORT_BY_DATE_DESCENDING
  //   ->  test_385397.js
});
