/* eslint-disable no-undef */
const PAGE_URL =
  "https://example.com/browser/dom/media/mediacontrol/tests/file_non_autoplay.html";
const PAGE2_URL =
  "https://example.com/browser/dom/media/mediacontrol/tests/file_main_frame_with_multiple_child_session_frames.html";
const IFRAME_URL =
  "https://example.com/browser/dom/media/mediacontrol/tests/file_iframe_media.html";
const CORS_IFRAME_URL =
  "https://example.org/browser/dom/media/mediacontrol/tests/file_iframe_media.html";
const CORS_IFRAME2_URL =
  "https://test1.example.org/browser/dom/media/mediacontrol/tests/file_iframe_media.html";
const videoId = "video";

/**
 * This test is used to check the scenario when we should use the customized
 * action handler and the the default action handler (play/pause/stop).
 * If a frame (DOM Window, it could be main frame or an iframe) has active media
 * session, then it should use the customized action handler it it has one.
 * Otherwise, the default action handler should be used.
 */
add_task(async function setupTestingPref() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["dom.media.mediasession.enabled", true],
      ["media.mediacontrol.testingevents.enabled", true],
    ],
  });
});

add_task(async function triggerDefaultActionHandler() {
  // Default handler should be triggered no matter if media session exists or not.
  const kCreateMediaSession = [true, false];
  for (const shouldCreateSession of kCreateMediaSession) {
    const kActions = ["play", "pause", "stop"];
    for (const action of kActions) {
      info(`test for '${action}', shouldCreateSession=${shouldCreateSession}`);
      info(`open page and start media`);
      const tab = await createTabAndLoad(PAGE_URL);
      await playMedia(tab, videoId);

      if (shouldCreateSession) {
        info(
          `media has started, so created session should become active session`
        );
        await Promise.all([
          waitUntilActiveMediaSessionChanged(),
          createMediaSession(tab),
        ]);
      }

      if (action == "play") {
        info(`pause media first in order to test 'play'`);
        await pauseMedia(tab, videoId);

        info(`press '${action}' should trigger default action handler`);
        await simulateMediaAction(tab, action);

        info(`default action handler should resume media`);
        await checkOrWaitUntilMediaPlays(tab, { videoId });
      } else {
        info(`press '${action}' should trigger default action handler`);
        await simulateMediaAction(tab, action);

        info(`default action handler should pause media`);
        await checkOrWaitUntilMediaPauses(tab, { videoId });
      }

      info(`remove tab`);
      await BrowserTestUtils.removeTab(tab);
    }
  }
});

add_task(async function triggerNonDefaultHandlerWhenSetCustomizedHandler() {
  const kActions = ["play", "pause", "stop"];
  for (const action of kActions) {
    info(`open page and start media`);
    const tab = await createTabAndLoad(PAGE_URL);
    await startMedia(tab, { videoId });

    info(`set action handler for '${action}'`);
    await setActionHandler(tab, action);

    info(`press '${action}' should trigger action handler (not a default one)`);
    await simulateMediaAction(tab, action);
    await waitUntilActionHandlerIsTriggered(tab, action);

    info(`action handler doesn't pause media, media should keep playing`);
    await checkOrWaitUntilMediaPlays(tab, { videoId });

    info(`remove tab`);
    await BrowserTestUtils.removeTab(tab);
  }
});

add_task(
  async function triggerDefaultHandlerToPausePlaybackOnInactiveSession() {
    const kIframeUrls = [IFRAME_URL, CORS_IFRAME_URL];
    for (const url of kIframeUrls) {
      const kActions = ["play", "pause", "stop"];
      for (const action of kActions) {
        info(`open page and load iframe`);
        const tab = await createTabAndLoad(PAGE_URL);
        const frameId = "iframe";
        await loadIframe(tab, frameId, url);

        info(`start media from iframe would make it become active session`);
        await startMedia(tab, { frameId });

        info(`press '${action}' should trigger iframe's action handler`);
        await setActionHandler(tab, action, frameId);
        await simulateMediaAction(tab, action);
        await waitUntilActionHandlerIsTriggered(tab, action, frameId);

        info(`start media from main frame so iframe would become inactive`);
        await Promise.all([
          waitUntilActiveMediaSessionChanged(),
          startMedia(tab, { videoId }),
        ]);

        if (action == "play") {
          info(`pause media first in order to test 'play'`);
          await pauseAllMedia(tab);

          info(
            `press '${action}' would trigger default andler on main frame because it doesn't set action handler`
          );
          await simulateMediaAction(tab, action);
          await checkOrWaitUntilMediaPlays(tab, { videoId });

          info(
            `default handler should also be triggered on inactive iframe, which would resume media`
          );
          await checkOrWaitUntilMediaPlays(tab, { frameId });
        } else {
          info(
            `press '${action}' would trigger default andler on main frame because it doesn't set action handler`
          );
          await simulateMediaAction(tab, action);
          await checkOrWaitUntilMediaPauses(tab, { videoId });

          info(
            `default handler should also be triggered on inactive iframe, which would pause media`
          );
          await checkOrWaitUntilMediaPauses(tab, { frameId });
        }

        info(`remove tab`);
        await BrowserTestUtils.removeTab(tab);
      }
    }
  }
);

add_task(async function onlyResumeActiveMediaSession() {
  info(`open page and load iframes`);
  const tab = await createTabAndLoad(PAGE2_URL);
  const frame1Id = "frame1";
  const frame2Id = "frame2";
  await loadIframe(tab, frame1Id, CORS_IFRAME_URL);
  await loadIframe(tab, frame2Id, CORS_IFRAME2_URL);

  info(`start media from iframe1 would make it become active session`);
  await createMediaSession(tab, frame1Id);
  await Promise.all([
    waitUntilActiveMediaSessionChanged(),
    startMedia(tab, { frameId: frame1Id }),
  ]);

  info(`start media from iframe2 would make it become active session`);
  await createMediaSession(tab, frame2Id);
  await Promise.all([
    waitUntilActiveMediaSessionChanged(),
    startMedia(tab, { frameId: frame2Id }),
  ]);

  info(`press 'pause' should pause both iframes`);
  await simulateMediaAction(tab, "pause");
  await checkOrWaitUntilMediaPauses(tab, { frameId: frame1Id });
  await checkOrWaitUntilMediaPauses(tab, { frameId: frame2Id });

  info(
    `press 'play' should only resume iframe2 which has active media session`
  );
  await simulateMediaAction(tab, "play");
  await checkOrWaitUntilMediaPauses(tab, { frameId: frame1Id });
  await checkOrWaitUntilMediaPlays(tab, { frameId: frame2Id });

  info(`remove tab`);
  await BrowserTestUtils.removeTab(tab);
});

/**
 * The following are helper functions.
 */
function startMedia(tab, { videoId, frameId }) {
  return SpecialPowers.spawn(
    tab.linkedBrowser,
    [videoId, frameId],
    (videoId, frameId) => {
      if (frameId) {
        return content.messageHelper(
          content.document.getElementById(frameId),
          "play",
          "played"
        );
      }
      return content.document.getElementById(videoId).play();
    }
  );
}

function pauseAllMedia(tab) {
  return SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
    await content.messageHelper(
      content.document.getElementById("iframe"),
      "pause",
      "paused"
    );
    const videos = content.document.getElementsByTagName("video");
    for (let video of videos) {
      video.pause();
    }
  });
}

function createMediaSession(tab, frameId = null) {
  info(`create media session`);
  return SpecialPowers.spawn(tab.linkedBrowser, [frameId], async frameId => {
    if (frameId) {
      await content.messageHelper(
        content.document.getElementById(frameId),
        "create-media-session",
        "created-media-session"
      );
      return;
    }
    // simply calling a media session would create an instance.
    content.navigator.mediaSession;
  });
}

function checkOrWaitUntilMediaPauses(tab, { videoId, frameId }) {
  return SpecialPowers.spawn(
    tab.linkedBrowser,
    [videoId, frameId],
    (videoId, frameId) => {
      if (frameId) {
        return content.messageHelper(
          content.document.getElementById(frameId),
          "check-pause",
          "checked-pause"
        );
      }
      return new Promise(r => {
        const video = content.document.getElementById(videoId);
        if (video.paused) {
          ok(true, `media stopped playing`);
          r();
        } else {
          info(`wait until media stops playing`);
          video.onpause = () => {
            video.onpause = null;
            ok(true, `media stopped playing`);
            r();
          };
        }
      });
    }
  );
}

function checkOrWaitUntilMediaPlays(tab, { videoId, frameId }) {
  return SpecialPowers.spawn(
    tab.linkedBrowser,
    [videoId, frameId],
    (videoId, frameId) => {
      if (frameId) {
        return content.messageHelper(
          content.document.getElementById(frameId),
          "check-playing",
          "checked-playing"
        );
      }
      return new Promise(r => {
        const video = content.document.getElementById(videoId);
        if (!video.paused) {
          ok(true, `media is playing`);
          r();
        } else {
          info(`wait until media starts playing`);
          video.onplay = () => {
            video.onplay = null;
            ok(true, `media starts playing`);
            r();
          };
        }
      });
    }
  );
}

function setActionHandler(tab, action, frameId = null) {
  return SpecialPowers.spawn(
    tab.linkedBrowser,
    [action, frameId],
    async (action, frameId) => {
      if (frameId) {
        await content.messageHelper(
          content.document.getElementById(frameId),
          {
            cmd: "setActionHandler",
            action,
          },
          "setActionHandler-done"
        );
        return;
      }
      // Create this on the first function call
      if (content.actionHandlerPromises === undefined) {
        content.actionHandlerPromises = {};
      }
      content.actionHandlerPromises[action] = new Promise(r => {
        content.navigator.mediaSession.setActionHandler(action, () => {
          info(`receive ${action}`);
          r();
        });
      });
    }
  );
}

async function waitUntilActionHandlerIsTriggered(tab, action, frameId = null) {
  info(`wait until '${action}' action handler is triggered`);
  return SpecialPowers.spawn(
    tab.linkedBrowser,
    [action, frameId],
    (action, frameId) => {
      if (frameId) {
        return content.messageHelper(
          content.document.getElementById(frameId),
          {
            cmd: "checkActionHandler",
            action,
          },
          "checkActionHandler-done"
        );
      }
      const actionTriggerPromise = content.actionHandlerPromises[action];
      ok(actionTriggerPromise, `Has created promise for ${action}`);
      return actionTriggerPromise;
    }
  );
}

async function simulateMediaAction(tab, action) {
  const controller = tab.linkedBrowser.browsingContext.mediaController;
  if (!controller.isActive) {
    await new Promise(r => (controller.onactivated = r));
  }
  MediaControlService.generateMediaControlKey(action);
}

function loadIframe(tab, iframeId, url) {
  return SpecialPowers.spawn(
    tab.linkedBrowser,
    [iframeId, url],
    async (iframeId, url) => {
      const iframe = content.document.getElementById(iframeId);
      info(`load iframe with url '${url}'`);
      iframe.src = url;
      await new Promise(r => (iframe.onload = r));
      // create a helper to simplify communication process with iframe
      content.messageHelper = (target, sentMessage, expectedResponse) => {
        target.contentWindow.postMessage(sentMessage, "*");
        return new Promise(r => {
          content.onmessage = event => {
            if (event.data == expectedResponse) {
              ok(true, `Received response ${expectedResponse}`);
              content.onmessage = null;
              r();
            }
          };
        });
      };
    }
  );
}

function waitUntilActiveMediaSessionChanged() {
  return BrowserUtils.promiseObserved("active-media-session-changed");
}
