/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";

Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);

let tmp = {};
Cu.import("resource:///modules/NewTabUtils.jsm", tmp);
let NewTabUtils = tmp.NewTabUtils;

registerCleanupFunction(function () {
  while (gBrowser.tabs.length > 1)
    gBrowser.removeTab(gBrowser.tabs[1]);

  Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
});

/**
 * We'll want to restore the original links provider later.
 */
let originalProvider = NewTabUtils.links._provider;

/**
 * Provide the default test function to start our test runner.
 */
function test() {
  TestRunner.run();
}

/**
 * The test runner that controls the execution flow of our tests.
 */
let TestRunner = {
  /**
   * Starts the test runner.
   */
  run: function () {
    waitForExplicitFinish();

    this._iter = runTests();
    this.next();
  },

  /**
   * Runs the next available test or finishes if there's no test left.
   */
  next: function () {
    try {
      TestRunner._iter.next();
    } catch (e if e instanceof StopIteration) {
      TestRunner.finish();
    }
  },

  /**
   * Finishes all tests and cleans up.
   */
  finish: function () {
    function cleanupAndFinish() {
      // Restore the old provider.
      NewTabUtils.links._provider = originalProvider;

      whenPagesUpdated(finish);
      NewTabUtils.restore();
    }

    let callbacks = NewTabUtils.links._populateCallbacks;
    let numCallbacks = callbacks.length;

    if (numCallbacks)
      callbacks.splice(0, numCallbacks, cleanupAndFinish);
    else
      cleanupAndFinish();
  }
};

/**
 * Returns the selected tab's content window.
 * @return The content window.
 */
function getContentWindow() {
  return gBrowser.selectedBrowser.contentWindow;
}

/**
 * Returns the selected tab's content document.
 * @return The content document.
 */
function getContentDocument() {
  return gBrowser.selectedBrowser.contentDocument;
}

/**
 * Returns the newtab grid of the selected tab.
 * @return The newtab grid.
 */
function getGrid() {
  return getContentWindow().gGrid;
}

/**
 * Returns the cell at the given index of the selected tab's newtab grid.
 * @param aIndex The cell index.
 * @return The newtab cell.
 */
function getCell(aIndex) {
  return getGrid().cells[aIndex];
}

/**
 * Allows to provide a list of links that is used to construct the grid.
 * @param aLinksPattern the pattern (see below)
 *
 * Example: setLinks("1,2,3")
 * Result: [{url: "about:blank#1", title: "site#1"},
 *          {url: "about:blank#2", title: "site#2"}
 *          {url: "about:blank#3", title: "site#3"}]
 */
function setLinks(aLinksPattern) {
  let links = aLinksPattern.split(/\s*,\s*/).map(function (id) {
    return {url: "about:blank#" + id, title: "site#" + id};
  });

  NewTabUtils.links._provider = {getLinks: function (c) c(links)};
  NewTabUtils.links._links = links;
}

/**
 * Allows to specify the list of pinned links (that have a fixed position in
 * the grid.
 * @param aLinksPattern the pattern (see below)
 *
 * Example: setPinnedLinks("3,,1")
 * Result: 'about:blank#3' is pinned in the first cell. 'about:blank#1' is
 *         pinned in the third cell.
 */
function setPinnedLinks(aLinksPattern) {
  let pinnedLinks = [];

  aLinksPattern.split(/\s*,\s*/).forEach(function (id, index) {
    let link;

    if (id)
      link = {url: "about:blank#" + id, title: "site#" + id};

    pinnedLinks[index] = link;
  });

  // Inject the list of pinned links to work with.
  NewTabUtils.pinnedLinks._links = pinnedLinks;
}

/**
 * Restore the grid state.
 */
function restore() {
  whenPagesUpdated();
  NewTabUtils.restore();
}

/**
 * Creates a new tab containing 'about:newtab'.
 */
function addNewTabPageTab() {
  let tab = gBrowser.selectedTab = gBrowser.addTab("about:newtab");
  let browser = tab.linkedBrowser;

  // Wait for the new tab page to be loaded.
  browser.addEventListener("load", function onLoad() {
    browser.removeEventListener("load", onLoad, true);

    if (NewTabUtils.allPages.enabled) {
      // Continue when the link cache has been populated.
      NewTabUtils.links.populateCache(function () {
        executeSoon(TestRunner.next);
      });
    } else {
      TestRunner.next();
    }
  }, true);
}

/**
 * Compares the current grid arrangement with the given pattern.
 * @param the pattern (see below)
 * @param the array of sites to compare with (optional)
 *
 * Example: checkGrid("3p,2,,1p")
 * Result: We expect the first cell to contain the pinned site 'about:blank#3'.
 *         The second cell contains 'about:blank#2'. The third cell is empty.
 *         The fourth cell contains the pinned site 'about:blank#4'.
 */
function checkGrid(aSitesPattern, aSites) {
  let length = aSitesPattern.split(",").length;
  let sites = (aSites || getGrid().sites).slice(0, length);
  let current = sites.map(function (aSite) {
    if (!aSite)
      return "";

    let pinned = aSite.isPinned();
    let pinButton = aSite.node.querySelector(".newtab-control-pin");
    let hasPinnedAttr = pinButton.hasAttribute("pinned");

    if (pinned != hasPinnedAttr)
      ok(false, "invalid state (site.isPinned() != site[pinned])");

    return aSite.url.replace(/^about:blank#(\d+)$/, "$1") + (pinned ? "p" : "");
  });

  is(current, aSitesPattern, "grid status = " + aSitesPattern);
}

/**
 * Blocks a site from the grid.
 * @param aIndex The cell index.
 */
function blockCell(aIndex) {
  whenPagesUpdated();
  getCell(aIndex).site.block();
}

/**
 * Pins a site on a given position.
 * @param aIndex The cell index.
 * @param aPinIndex The index the defines where the site should be pinned.
 */
function pinCell(aIndex, aPinIndex) {
  getCell(aIndex).site.pin(aPinIndex);
}

/**
 * Unpins the given cell's site.
 * @param aIndex The cell index.
 */
function unpinCell(aIndex) {
  whenPagesUpdated();
  getCell(aIndex).site.unpin();
}

/**
 * Simulates a drop and drop operation.
 * @param aDropIndex The cell index of the drop target.
 * @param aDragIndex The cell index containing the dragged site (optional).
 */
function simulateDrop(aDropIndex, aDragIndex) {
  let draggedSite;
  let {gDrag: drag, gDrop: drop} = getContentWindow();
  let event = createDragEvent("drop", "about:blank#99\nblank");

  if (typeof aDragIndex != "undefined")
    draggedSite = getCell(aDragIndex).site;

  if (draggedSite)
    drag.start(draggedSite, event);

  whenPagesUpdated();
  drop.drop(getCell(aDropIndex), event);

  if (draggedSite)
    drag.end(draggedSite);
}

/**
 * Sends a custom drag event to a given DOM element.
 * @param aEventType The drag event's type.
 * @param aTarget The DOM element that the event is dispatched to.
 * @param aData The event's drag data (optional).
 */
function sendDragEvent(aEventType, aTarget, aData) {
  let event = createDragEvent(aEventType, aData);
  let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor);
  let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
  windowUtils.dispatchDOMEventViaPresShell(aTarget, event, true);
}

/**
 * Creates a custom drag event.
 * @param aEventType The drag event's type.
 * @param aData The event's drag data (optional).
 * @return The drag event.
 */
function createDragEvent(aEventType, aData) {
  let dataTransfer = {
    mozUserCancelled: false,
    setData: function () null,
    setDragImage: function () null,
    getData: function () aData,

    types: {
      contains: function (aType) aType == "text/x-moz-url"
    },

    mozGetDataAt: function (aType, aIndex) {
      if (aIndex || aType != "text/x-moz-url")
        return null;

      return aData;
    }
  };

  let event = getContentDocument().createEvent("DragEvents");
  event.initDragEvent(aEventType, true, true, getContentWindow(), 0, 0, 0, 0, 0,
                      false, false, false, false, 0, null, dataTransfer);

  return event;
}

/**
 * Resumes testing when all pages have been updated.
 */
function whenPagesUpdated(aCallback) {
  let page = {
    update: function () {
      NewTabUtils.allPages.unregister(this);
      executeSoon(aCallback || TestRunner.next);
    }
  };

  NewTabUtils.allPages.register(page);
  registerCleanupFunction(function () {
    NewTabUtils.allPages.unregister(page);
  });
}
