"use strict";

const TEST_ENGINE_NAME = "Foo";
const TEST_ENGINE_BASENAME = "testEngine.xml";
const SEARCHBAR_BASE_ID = "searchbar-engine-one-off-item-";

let originalEngine;
let originalPrivateEngine;

async function resetEngines() {
  await Services.search.setDefault(
    originalEngine,
    Ci.nsISearchService.CHANGE_REASON_UNKNOWN
  );
  await Services.search.setDefaultPrivate(
    originalPrivateEngine,
    Ci.nsISearchService.CHANGE_REASON_UNKNOWN
  );
}

registerCleanupFunction(resetEngines);

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["browser.search.separatePrivateDefault.ui.enabled", true],
      ["browser.search.separatePrivateDefault", true],
      ["browser.search.widget.inNavBar", true],
    ],
  });
  originalEngine = await Services.search.getDefault();
  originalPrivateEngine = await Services.search.getDefaultPrivate();
  registerCleanupFunction(async () => {
    await resetEngines();
  });

  await SearchTestUtils.promiseNewSearchEngine({
    url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
  });
});

async function testSearchBarChangeEngine(win, testPrivate, isPrivateWindow) {
  info(
    `Testing search bar with testPrivate: ${testPrivate} isPrivateWindow: ${isPrivateWindow}`
  );

  const searchPopup = win.document.getElementById("PopupSearchAutoComplete");
  const searchOneOff = searchPopup.oneOffButtons;

  // Ensure the engine is reset.
  await resetEngines();

  let oneOffButton = await openPopupAndGetEngineButton(
    searchPopup,
    searchOneOff,
    SEARCHBAR_BASE_ID,
    TEST_ENGINE_NAME
  );

  const contextMenu = searchOneOff.contextMenuPopup;
  const setDefaultEngineMenuItem = searchOneOff.querySelector(
    ".search-one-offs-context-set-default" + (testPrivate ? "-private" : "")
  );

  // Click the set default engine menu item.
  let promise = promiseDefaultEngineChanged(testPrivate);
  contextMenu.activateItem(setDefaultEngineMenuItem);

  // This also checks the engine correctly changed.
  await promise;

  if (testPrivate == isPrivateWindow) {
    let expectedName = originalEngine.name;
    let expectedImage = originalEngine.iconURI.spec;
    if (isPrivateWindow) {
      expectedName = originalPrivateEngine.name;
      expectedImage = originalPrivateEngine.iconURI.spec;
    }

    Assert.equal(
      oneOffButton.getAttribute("tooltiptext"),
      expectedName,
      "Should now have the original engine's name for the tooltip"
    );
    Assert.equal(
      oneOffButton.image,
      expectedImage,
      "Should now have the original engine's uri for the image"
    );
  }

  await promiseClosePopup(searchPopup);
}

add_task(async function test_searchBarChangeEngine() {
  await testSearchBarChangeEngine(window, false, false);
  await testSearchBarChangeEngine(window, true, false);
});

add_task(async function test_searchBarChangeEngine_privateWindow() {
  const win = await BrowserTestUtils.openNewBrowserWindow({
    private: true,
  });
  await testSearchBarChangeEngine(win, true, true);
  await BrowserTestUtils.closeWindow(win);
});

/**
 * Promises that an engine change has happened for the current engine, which
 * has resulted in the test engine now being the current engine.
 *
 * @param {boolean} testPrivate
 *   Set to true if we're testing the private default engine.
 * @returns {Promise} Resolved once the test engine is set as the current engine.
 */
function promiseDefaultEngineChanged(testPrivate) {
  const expectedNotification = testPrivate
    ? "engine-default-private"
    : "engine-default";
  return new Promise(resolve => {
    function observer(aSub, aTopic, aData) {
      if (aData == expectedNotification) {
        Assert.equal(
          Services.search[
            testPrivate ? "defaultPrivateEngine" : "defaultEngine"
          ].name,
          TEST_ENGINE_NAME,
          "defaultEngine set"
        );
        Services.obs.removeObserver(observer, "browser-search-engine-modified");
        resolve();
      }
    }

    Services.obs.addObserver(observer, "browser-search-engine-modified");
  });
}

/**
 * Opens the specified search popup and gets the test engine from the
 * one-off buttons.
 *
 * @param {object} popup The expected popup.
 * @param {object} oneOffInstance The expected one-off instance for the popup.
 * @param {string} baseId The expected string for the id of the current
 *                        engine button, without the engine name.
 * @param {string} engineName The engine name for finding the one-off button.
 * @returns {object} Returns an object that represents the one off button for the
 *                          test engine.
 */
async function openPopupAndGetEngineButton(
  popup,
  oneOffInstance,
  baseId,
  engineName
) {
  const win = oneOffInstance.container.ownerGlobal;
  // Open the popup.
  win.gURLBar.blur();
  let shownPromise = promiseEvent(popup, "popupshown");
  let builtPromise = promiseEvent(oneOffInstance, "rebuild");
  let searchbar = win.document.getElementById("searchbar");
  let searchIcon = searchbar.querySelector(".searchbar-search-button");
  // Use the search icon to avoid hitting the network.
  EventUtils.synthesizeMouseAtCenter(searchIcon, {}, win);
  await Promise.all([shownPromise, builtPromise]);

  const contextMenu = oneOffInstance.contextMenuPopup;
  let oneOffButton = oneOffInstance.buttons;

  // Get the one-off button for the test engine.
  for (
    oneOffButton = oneOffButton.firstChild;
    oneOffButton;
    oneOffButton = oneOffButton.nextSibling
  ) {
    if (
      oneOffButton.nodeType == Node.ELEMENT_NODE &&
      oneOffButton.engine &&
      oneOffButton.engine.name == engineName
    ) {
      break;
    }
  }

  Assert.notEqual(
    oneOffButton,
    undefined,
    "One-off for test engine should exist"
  );
  Assert.equal(
    oneOffButton.getAttribute("tooltiptext"),
    engineName,
    "One-off should have the tooltip set to the engine name"
  );

  Assert.ok(
    oneOffButton.id.startsWith(baseId + "engine-"),
    "Should have an appropriate id"
  );

  // Open the context menu on the one-off.
  let promise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
  EventUtils.synthesizeMouseAtCenter(
    oneOffButton,
    {
      type: "contextmenu",
      button: 2,
    },
    win
  );
  await promise;

  return oneOffButton;
}

/**
 * Closes the popup and moves the mouse away from it.
 *
 * @param {Button} popup The popup to close.
 */
async function promiseClosePopup(popup) {
  // close the panel using the escape key.
  let promise = promiseEvent(popup, "popuphidden");
  EventUtils.synthesizeKey("KEY_Escape", {}, popup.ownerGlobal);
  await promise;

  // Move the cursor out of the panel area to avoid messing with other tests.
  EventUtils.synthesizeNativeMouseEvent({
    type: "mousemove",
    target: popup,
    offsetX: 0,
    offsetY: 0,
    win: popup.ownerGlobal,
  });
}
