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

/* This is a JavaScript module (JSM) to be imported via
 * ChromeUtils.import() and acts as a singleton. Only the following
 * listed symbols will exposed on import, and only when and where imported.
 */

import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs";

import { PlacesSyncUtils } from "resource://gre/modules/PlacesSyncUtils.sys.mjs";

import { Logger } from "resource://tps/logger.sys.mjs";

export var DumpHistory = async function TPS_History__DumpHistory() {
  let query = PlacesUtils.history.getNewQuery();
  let options = PlacesUtils.history.getNewQueryOptions();
  let root = PlacesUtils.history.executeQuery(query, options).root;
  root.containerOpen = true;
  Logger.logInfo("\n\ndumping history\n", true);
  for (var i = 0; i < root.childCount; i++) {
    let node = root.getChild(i);
    let uri = node.uri;
    let guid = await PlacesSyncUtils.history
      .fetchGuidForURL(uri)
      .catch(() => "?".repeat(12));
    let curvisits = await PlacesSyncUtils.history.fetchVisitsForURL(uri);
    for (var visit of curvisits) {
      Logger.logInfo(
        `GUID: ${guid}, URI: ${uri}, type=${visit.type}, date=${visit.date}`,
        true
      );
    }
  }
  root.containerOpen = false;
  Logger.logInfo("\nend history dump\n", true);
};

/**
 * HistoryEntry object
 *
 * Contains methods for manipulating browser history entries.
 */
export var HistoryEntry = {
  /**
   * Add
   *
   * Adds visits for a uri to the history database.  Throws on error.
   *
   * @param item An object representing one or more visits to a specific uri
   * @param usSinceEpoch The number of microseconds from Epoch to
   *        the time the current Crossweave run was started
   * @return nothing
   */
  async Add(item, msSinceEpoch) {
    Logger.AssertTrue(
      "visits" in item && "uri" in item,
      "History entry in test file must have both 'visits' " +
        "and 'uri' properties"
    );
    let place = {
      url: item.uri,
      visits: [],
    };
    for (let visit of item.visits) {
      let date = new Date(
        Math.round(msSinceEpoch + visit.date * 60 * 60 * 1000)
      );
      place.visits.push({ date, transition: visit.type });
    }
    if ("title" in item) {
      place.title = item.title;
    }
    return PlacesUtils.history.insert(place);
  },

  /**
   * Find
   *
   * Finds visits for a uri to the history database.  Throws on error.
   *
   * @param item An object representing one or more visits to a specific uri
   * @param usSinceEpoch The number of microseconds from Epoch to
   *        the time the current Crossweave run was started
   * @return true if all the visits for the uri are found, otherwise false
   */
  async Find(item, msSinceEpoch) {
    Logger.AssertTrue(
      "visits" in item && "uri" in item,
      "History entry in test file must have both 'visits' " +
        "and 'uri' properties"
    );
    let curvisits = await PlacesSyncUtils.history.fetchVisitsForURL(item.uri);
    for (let visit of curvisits) {
      for (let itemvisit of item.visits) {
        // Note: in microseconds.
        let expectedDate =
          itemvisit.date * 60 * 60 * 1000 * 1000 + msSinceEpoch * 1000;
        if (visit.type == itemvisit.type) {
          if (itemvisit.date === undefined || visit.date == expectedDate) {
            itemvisit.found = true;
          }
        }
      }
    }

    let all_items_found = true;
    for (let itemvisit of item.visits) {
      all_items_found = all_items_found && "found" in itemvisit;
      Logger.logInfo(
        `History entry for ${item.uri}, type: ${itemvisit.type}, date: ${itemvisit.date}` +
          `(${
            itemvisit.date * 60 * 60 * 1000 * 1000
          }), found = ${!!itemvisit.found}`
      );
    }
    return all_items_found;
  },

  /**
   * Delete
   *
   * Removes visits from the history database. Throws on error.
   *
   * @param item An object representing items to delete
   * @param usSinceEpoch The number of microseconds from Epoch to
   *        the time the current Crossweave run was started
   * @return nothing
   */
  async Delete(item, msSinceEpoch) {
    if ("uri" in item) {
      let removedAny = await PlacesUtils.history.remove(item.uri);
      if (!removedAny) {
        Logger.log("Warning: Removed 0 history visits for uri " + item.uri);
      }
    } else if ("host" in item) {
      await PlacesUtils.history.removeByFilter({ host: item.host });
    } else if ("begin" in item && "end" in item) {
      let filter = {
        beginDate: new Date(msSinceEpoch + item.begin * 60 * 60 * 1000),
        endDate: new Date(msSinceEpoch + item.end * 60 * 60 * 1000),
      };
      let removedAny = await PlacesUtils.history.removeVisitsByFilter(filter);
      if (!removedAny) {
        Logger.log(
          "Warning: Removed 0 history visits with " +
            JSON.stringify({ item, filter })
        );
      }
    } else {
      Logger.AssertTrue(
        false,
        "invalid entry in delete history " + JSON.stringify(item)
      );
    }
  },
};
