// This file supports translating W3C tests
// to tests on auto MochiTest system with minimum changes.
// Author: Maksim Lebedev <alessarik@gmail.com>

/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */

// Function allows to prepare our tests after load document
addEventListener(
  "load",
  function (event) {
    console.log("OnLoad external document");
    prepareTest();
  },
  false
);

// Function allows to initialize prerequisites before testing
function prepareTest() {
  SimpleTest.waitForExplicitFinish();
  SimpleTest.requestCompleteLog();
  startTest();
}

function setImplicitPointerCapture(capture, callback) {
  console.log("SET dom.w3c_pointer_events.implicit_capture as " + capture);
  SpecialPowers.pushPrefEnv(
    {
      set: [["dom.w3c_pointer_events.implicit_capture", capture]],
    },
    callback
  );
}

var utils = SpecialPowers.Ci.nsIDOMWindowUtils;

// Mouse Event Helper Object
var MouseEventHelper = (function () {
  return {
    MOUSE_ID: utils.DEFAULT_MOUSE_POINTER_ID,
    PEN_ID: utils.DEFAULT_PEN_POINTER_ID,
    // State
    // TODO: Separate this to support mouse and pen simultaneously.
    BUTTONS_STATE: utils.MOUSE_BUTTONS_NO_BUTTON,

    // Button
    BUTTON_NONE: -1, // Used by test framework only. (replaced before sending)
    BUTTON_LEFT: utils.MOUSE_BUTTON_LEFT_BUTTON,
    BUTTON_MIDDLE: utils.MOUSE_BUTTON_MIDDLE_BUTTON,
    BUTTON_RIGHT: utils.MOUSE_BUTTON_RIGHT_BUTTON,

    // Buttons
    BUTTONS_NONE: utils.MOUSE_BUTTONS_NO_BUTTON,
    BUTTONS_LEFT: utils.MOUSE_BUTTONS_LEFT_BUTTON,
    BUTTONS_MIDDLE: utils.MOUSE_BUTTONS_MIDDLE_BUTTON,
    BUTTONS_RIGHT: utils.MOUSE_BUTTONS_RIGHT_BUTTON,
    BUTTONS_4TH: utils.MOUSE_BUTTONS_4TH_BUTTON,
    BUTTONS_5TH: utils.MOUSE_BUTTONS_5TH_BUTTON,

    // Utils
    computeButtonsMaskFromButton(aButton) {
      // Since the range of button values is 0 ~ 2 (see nsIDOMWindowUtils.idl),
      // we can use an array to find out the desired mask.
      var mask = [
        this.BUTTONS_NONE, // -1 (MouseEventHelper.BUTTON_NONE)
        this.BUTTONS_LEFT, // 0
        this.BUTTONS_MIDDLE, // 1
        this.BUTTONS_RIGHT, // 2
      ][aButton + 1];

      ok(mask !== undefined, "Unrecognized button value caught!");
      return mask;
    },

    checkExitState() {
      ok(!this.BUTTONS_STATE, "Mismatched mousedown/mouseup caught.");
    },
  };
})();

function createMouseEvent(aEventType, aParams) {
  var eventObj = { type: aEventType };

  // Default to mouse.
  eventObj.inputSource =
    aParams && "inputSource" in aParams
      ? aParams.inputSource
      : MouseEvent.MOZ_SOURCE_MOUSE;
  // Compute pointerId
  eventObj.id =
    eventObj.inputSource === MouseEvent.MOZ_SOURCE_MOUSE
      ? MouseEventHelper.MOUSE_ID
      : MouseEventHelper.PEN_ID;
  // Check or generate a |button| value.
  var isButtonEvent = aEventType === "mouseup" || aEventType === "mousedown";

  // Set |button| to the default value first.
  eventObj.button = isButtonEvent
    ? MouseEventHelper.BUTTON_LEFT
    : MouseEventHelper.BUTTON_NONE;

  // |button| is passed, use and check it.
  if (aParams && "button" in aParams) {
    var hasButtonValue = aParams.button !== MouseEventHelper.BUTTON_NONE;
    ok(
      !isButtonEvent || hasButtonValue,
      "Inappropriate |button| value caught."
    );
    eventObj.button = aParams.button;
  }

  // Generate a |buttons| value and update buttons state
  var buttonsMask = MouseEventHelper.computeButtonsMaskFromButton(
    eventObj.button
  );
  switch (aEventType) {
    case "mousedown":
      MouseEventHelper.BUTTONS_STATE |= buttonsMask; // Set button flag.
      break;
    case "mouseup":
      MouseEventHelper.BUTTONS_STATE &= ~buttonsMask; // Clear button flag.
      break;
  }
  eventObj.buttons = MouseEventHelper.BUTTONS_STATE;

  // Replace the button value for mousemove events.
  // Since in widget level design, even when no button is pressed at all, the
  // value of WidgetMouseEvent.button is still 0, which is the same value as
  // the one for mouse left button.
  if (aEventType === "mousemove") {
    eventObj.button = MouseEventHelper.BUTTON_LEFT;
  }
  return eventObj;
}

// Helper function to send MouseEvent with different parameters
function sendMouseEvent(int_win, elemId, mouseEventType, params) {
  var elem = int_win.document.getElementById(elemId);
  if (elem) {
    var rect = elem.getBoundingClientRect();
    var eventObj = createMouseEvent(mouseEventType, params);

    // Default to the center of the target element but we can still send to a
    // position outside of the target element.
    var offsetX =
      params && "offsetX" in params ? params.offsetX : rect.width / 2;
    var offsetY =
      params && "offsetY" in params ? params.offsetY : rect.height / 2;

    console.log(elemId, eventObj);
    synthesizeMouse(elem, offsetX, offsetY, eventObj, int_win);
  } else {
    is(!!elem, true, "Document should have element with id: " + elemId);
  }
}

// Helper function to send MouseEvent with position
function sendMouseEventAtPoint(aWindow, aLeft, aTop, aMouseEventType, aParams) {
  var eventObj = createMouseEvent(aMouseEventType, aParams);
  console.log(eventObj);
  synthesizeMouseAtPoint(aLeft, aTop, eventObj, aWindow);
}

// Touch Event Helper Object
var TouchEventHelper = {
  // State
  TOUCH_ID: utils.DEFAULT_TOUCH_POINTER_ID,
  TOUCH_STATE: false,

  // Utils
  checkExitState() {
    ok(!this.TOUCH_STATE, "Mismatched touchstart/touchend caught.");
  },
};

// Helper function to send TouchEvent with different parameters
// TODO: Support multiple touch points to test more features such as
// PointerEvent.isPrimary and pinch-zoom.
function sendTouchEvent(int_win, elemId, touchEventType, params) {
  var elem = int_win.document.getElementById(elemId);
  if (elem) {
    var rect = elem.getBoundingClientRect();
    var eventObj = {
      type: touchEventType,
      id: TouchEventHelper.TOUCH_ID,
    };

    // Update touch state
    switch (touchEventType) {
      case "touchstart":
        TouchEventHelper.TOUCH_STATE = true; // Set touch flag.
        break;
      case "touchend":
      case "touchcancel":
        TouchEventHelper.TOUCH_STATE = false; // Clear touch flag.
        break;
    }

    // Default to the center of the target element but we can still send to a
    // position outside of the target element.
    var offsetX =
      params && "offsetX" in params ? params.offsetX : rect.width / 2;
    var offsetY =
      params && "offsetY" in params ? params.offsetY : rect.height / 2;

    console.log(elemId, eventObj);
    synthesizeTouch(elem, offsetX, offsetY, eventObj, int_win);
  } else {
    is(!!elem, true, "Document should have element with id: " + elemId);
  }
}

// Helper function to trigger drag and drop.
async function doDragAndDrop(int_win, srcElemId, destElemId, params = {}) {
  params.srcElement = int_win.document.getElementById(srcElemId);
  params.destElement = int_win.document.getElementById(destElemId);
  params.srcWindow = int_win;
  params.destWindow = int_win;
  params.id = MouseEventHelper.MOUSE_ID;
  // This is basically for android which has a larger drag threshold.
  params.stepY = params.stepY || 25;
  await synthesizePlainDragAndDrop(params);
}

// Helper function to run Point Event test in a new tab.
function runTestInNewWindow(aFile) {
  var testURL =
    location.href.substring(0, location.href.lastIndexOf("/") + 1) + aFile;
  var testWindow = window.open(testURL, "_blank");
  var testDone = false;

  // We start testing when receiving load event. Inject the mochitest helper js
  // to the test case after DOM elements are constructed and before the load
  // event is fired.
  testWindow.addEventListener(
    "DOMContentLoaded",
    function () {
      var e = testWindow.document.createElement("script");
      e.type = "text/javascript";
      e.src =
        "../".repeat(aFile.split("/").length - 1) +
        "mochitest_support_internal.js";
      testWindow.document.getElementsByTagName("head")[0].appendChild(e);
    },
    { once: true }
  );

  window.addEventListener("message", function (aEvent) {
    switch (aEvent.data.type) {
      case "START":
        // Update constants
        MouseEventHelper.MOUSE_ID = aEvent.data.message.mouseId;
        MouseEventHelper.PEN_ID = aEvent.data.message.penId;
        TouchEventHelper.TOUCH_ID = aEvent.data.message.touchId;

        executeTest(testWindow);
        break;
      case "RESULT":
        // Should not perform checking after SimpleTest.finish().
        if (!testDone) {
          ok(aEvent.data.result, aEvent.data.message);
        }
        break;
      case "FIN":
        testDone = true;
        MouseEventHelper.checkExitState();
        TouchEventHelper.checkExitState();
        testWindow.close();
        SimpleTest.finish();
        break;
    }
  });
}
