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

/* eslint-env mozilla/frame-script */

"use strict";

(function() {

const TEST_MSG = "ContentSearchUIControllerTest";
const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml";
var gController;

addMessageListener(TEST_MSG, msg => {
  messageHandlers[msg.data.type](msg.data.data);
});

var messageHandlers = {

  init() {
    Services.search.currentEngine = Services.search.getEngineByName(ENGINE_NAME);
    let input = content.document.querySelector("input");
    gController =
      new content.ContentSearchUIController(input, input.parentNode, "test", "test");
    content.addEventListener("ContentSearchService", function listener(aEvent) {
      if (aEvent.detail.type == "State" &&
          gController.defaultEngine.name == ENGINE_NAME) {
        content.removeEventListener("ContentSearchService", listener);
        ack("init");
      }
    });
  },

  key(arg) {
    let keyName = typeof(arg) == "string" ? arg : arg.key;
    content.synthesizeKey(keyName, arg.modifiers);
    let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
    wait(ack.bind(null, "key"));
  },

  startComposition(arg) {
    content.synthesizeComposition({ type: "compositionstart", data: "" });
    ack("startComposition");
  },

  changeComposition(arg) {
    let data = typeof(arg) == "string" ? arg : arg.data;
    content.synthesizeCompositionChange({
      composition: {
        string: data,
        clauses: [
          { length: data.length, attr: content.COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      caret: { start: data.length, length: 0 }
    });
    let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
    wait(ack.bind(null, "changeComposition"));
  },

  commitComposition() {
    content.synthesizeComposition({ type: "compositioncommitasis" });
    ack("commitComposition");
  },

  focus() {
    gController.input.focus();
    ack("focus");
  },

  blur() {
    gController.input.blur();
    ack("blur");
  },

  waitForSearch() {
    waitForContentSearchEvent("Search", aData => ack("waitForSearch", aData));
  },

  waitForSearchSettings() {
    waitForContentSearchEvent("ManageEngines",
                              aData => ack("waitForSearchSettings", aData));
  },

  mousemove(itemIndex) {
    let row;
    if (itemIndex == -1) {
      row = gController._table.firstChild;
    } else {
      let allElts = [...gController._suggestionsList.children,
                     ...gController._oneOffButtons,
                     content.document.getElementById("contentSearchSettingsButton")];
      row = allElts[itemIndex];
    }
    let event = {
      type: "mousemove",
      clickcount: 0,
    };
    row.addEventListener("mousemove", function() {
      ack("mousemove");
    }, {once: true});
    content.synthesizeMouseAtCenter(row, event);
  },

  click(arg) {
    let eltIdx = typeof(arg) == "object" ? arg.eltIdx : arg;
    let row;
    if (eltIdx == -1) {
      row = gController._table.firstChild;
    } else {
      let allElts = [...gController._suggestionsList.children,
                     ...gController._oneOffButtons,
                     content.document.getElementById("contentSearchSettingsButton")];
      row = allElts[eltIdx];
    }
    let event = arg.modifiers || {};
    // synthesizeMouseAtCenter defaults to sending a mousedown followed by a
    // mouseup if the event type is not specified.
    content.synthesizeMouseAtCenter(row, event);
    ack("click");
  },

  addInputValueToFormHistory() {
    gController.addInputValueToFormHistory();
    ack("addInputValueToFormHistory");
  },

  addDuplicateOneOff() {
    let btn = gController._oneOffButtons[gController._oneOffButtons.length - 1];
    let newBtn = btn.cloneNode(true);
    btn.parentNode.appendChild(newBtn);
    gController._oneOffButtons.push(newBtn);
    ack("addDuplicateOneOff");
  },

  removeLastOneOff() {
    gController._oneOffButtons.pop().remove();
    ack("removeLastOneOff");
  },

  reset() {
    // Reset both the input and suggestions by select all + delete. If there was
    // no text entered, this won't have any effect, so also escape to ensure the
    // suggestions table is closed.
    gController.input.focus();
    content.synthesizeKey("a", {accelKey: true});
    content.synthesizeKey("KEY_Delete");
    content.synthesizeKey("KEY_Escape");
    ack("reset");
  },
};

function ack(aType, aData) {
  sendAsyncMessage(TEST_MSG, { type: aType, data: aData || currentState() });
}

function waitForSuggestions(cb) {
  let observer = new content.MutationObserver(() => {
    if (gController.input.getAttribute("aria-expanded") == "true") {
      observer.disconnect();
      cb();
    }
  });
  observer.observe(gController.input, {
    attributes: true,
    attributeFilter: ["aria-expanded"],
  });
}

function waitForContentSearchEvent(messageType, cb) {
  let mm = content.SpecialPowers.Cc["@mozilla.org/globalmessagemanager;1"].getService();
  mm.addMessageListener("ContentSearch", function listener(aMsg) {
    if (aMsg.data.type != messageType) {
      return;
    }
    mm.removeMessageListener("ContentSearch", listener);
    cb(aMsg.data.data);
  });
}

function currentState() {
  let state = {
    selectedIndex: gController.selectedIndex,
    selectedButtonIndex: gController.selectedButtonIndex,
    numSuggestions: gController._table.hidden ? 0 : gController.numSuggestions,
    suggestionAtIndex: [],
    isFormHistorySuggestionAtIndex: [],

    tableHidden: gController._table.hidden,

    inputValue: gController.input.value,
    ariaExpanded: gController.input.getAttribute("aria-expanded"),
  };

  if (state.numSuggestions) {
    for (let i = 0; i < gController.numSuggestions; i++) {
      state.suggestionAtIndex.push(gController.suggestionAtIndex(i));
      state.isFormHistorySuggestionAtIndex.push(
        gController.isFormHistorySuggestionAtIndex(i));
    }
  }

  return state;
}

})();
