/**
 * This test is used for testing whether WebAudio can be started correctly in
 * different scenarios, such as
 * 1) site has existing 'autoplay-media' permission for allowing autoplay
 * 2) site has existing 'autoplay-media' permission for blocking autoplay
 * 3) site doesn't have permission, start audio context by calling resume() or
 *    AudioScheduledNode.start() after granting user activation.
 */
"use strict";

ChromeUtils.import("resource:///modules/SitePermissions.jsm", this);
const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_empty.html";

function setup_test_preference() {
  return SpecialPowers.pushPrefEnv({"set": [
    ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
    ["media.autoplay.enabled.user-gestures-needed", true],
    ["media.autoplay.block-webaudio", true],
    ["media.autoplay.block-event.enabled", true],
  ]});
}

function createAudioContext() {
  content.ac = new content.AudioContext();
  const ac = content.ac;

  ac.allowedToStart = new Promise(resolve => {
    ac.addEventListener("statechange", function() {
      if (ac.state === "running") {
        resolve();
      }
    }, {once: true});
  });

  ac.notAllowedToStart = new Promise(resolve => {
    ac.addEventListener("blocked", function() {
      resolve();
    }, {once: true});
  });
}

async function checkIfAudioContextIsAllowedToStart(isAllowedToStart) {
  const ac = content.ac;
  if (isAllowedToStart) {
    await ac.allowedToStart;
    ok(ac.state === "running", `AudioContext is running.`);
  } else {
    await ac.notAllowedToStart;
    ok(ac.state === "suspended", `AudioContext is not started yet.`);
  }
}

async function resumeAudioContext(isAllowedToStart) {
  const ac = content.ac;
  const resumePromise = ac.resume();
  const blockedPromise = new Promise(resolve => {
    ac.addEventListener("blocked", function() {
      resolve();
    }, {once: true});
  });

  if (isAllowedToStart) {
    await resumePromise;
    ok(true, `successfully resume AudioContext.`);
  } else {
    await blockedPromise;
    ok(true, `resume is blocked because AudioContext is not allowed to start.`);
  }
}

function startAudioContext(method) {
  const ac = content.ac;
  if (method == "AudioContext") {
    info(`using AudioContext.resume() to start AudioContext`);
    ac.resume();
    return;
  }
  info(`using ${method}.start() to start AudioContext`);
  let node;
  switch (method) {
    case "AudioBufferSourceNode":
      node = ac.createBufferSource();
      break;
    case "ConstantSourceNode":
      node = ac.createConstantSource();
      break;
    case "OscillatorNode":
      node = ac.createOscillator();
      break;
    default:
      ok(false, "undefined AudioScheduledSourceNode type");
      return;
  }
  node.connect(ac.destination);
  node.start();
}

async function testAutoplayExistingPermission({name, permission}) {
  info(`- starting \"${name}\" -`);
  const tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, PAGE);
  const browser = tab.linkedBrowser;

  info(`- set the 'autoplay-media' permission -`);
  const promptShow = () =>
    PopupNotifications.getNotification("autoplay-media", browser);
  SitePermissions.set(browser.currentURI, "autoplay-media", permission);
  ok(!promptShow(), `should not be showing permission prompt yet`);

  info(`- create audio context -`);
  loadFrameScript(browser, createAudioContext);

  info(`- check AudioContext status -`);
  const isAllowedToStart = permission === SitePermissions.ALLOW;
  await ContentTask.spawn(browser, isAllowedToStart,
                          checkIfAudioContextIsAllowedToStart);
  await ContentTask.spawn(browser, isAllowedToStart,
                          resumeAudioContext);

  info(`- remove tab -`);
  SitePermissions.remove(browser.currentURI, "autoplay-media");
  await BrowserTestUtils.removeTab(tab);
}

async function testAutoplayUnknownPermission({name, method}) {
  info(`- starting \"${name}\" -`);
  const tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, PAGE);
  const browser = tab.linkedBrowser;

  info(`- set the 'autoplay-media' permission to UNKNOWN -`);
  const promptShow = () =>
    PopupNotifications.getNotification("autoplay-media", browser);
  SitePermissions.set(browser.currentURI, "autoplay-media", SitePermissions.UNKNOWN);
  ok(!promptShow(), `should not be showing permission prompt yet`);

  info(`- create AudioContext which should not start -`);
  loadFrameScript(browser, createAudioContext);
  await ContentTask.spawn(browser, false, checkIfAudioContextIsAllowedToStart);

  info(`- simulate user activate the page -`);
  await ContentTask.spawn(browser, null, () => {
    content.document.notifyUserGestureActivation();
  });

  info(`- try to start AudioContext -`);
  await ContentTask.spawn(browser, method, startAudioContext);

  info(`- check AudioContext status -`);
  await ContentTask.spawn(browser, true /* allow to start */,
                          checkIfAudioContextIsAllowedToStart);
  await ContentTask.spawn(browser, true /* allow to start */,
                          resumeAudioContext);

  info(`- remove tab -`);
  SitePermissions.remove(browser.currentURI, "autoplay-media");
  await BrowserTestUtils.removeTab(tab);
}

add_task(async function start_tests() {
  info("- setup test preference -");
  await setup_test_preference();

  await testAutoplayExistingPermission({
    name: "Prexisting allow permission",
    permission: SitePermissions.ALLOW,
  });
  await testAutoplayExistingPermission({
    name: "Prexisting block permission",
    permission: SitePermissions.BLOCK,
  });
  const startMethods = ["AudioContext", "AudioBufferSourceNode",
                        "ConstantSourceNode", "OscillatorNode"];
  for (let method of startMethods) {
    await testAutoplayUnknownPermission({
      name: "Unknown permission and start AudioContext after granting user activation",
      method,
    });
  }
});
