// This test is called from both test_clipboard_editor.html and test_clipboard_noeditor.html
// This is to test that the code works both in the presence of a contentEditable node, and in the absense of one
var WATCH_TIMEOUT = 300;

// Some global variables to make the debug messages easier to track down
var gTestN0 = 0,
  gTestN1 = 0,
  gTestN2 = 0;
function testLoc() {
  return " " + gTestN0 + " - " + gTestN1 + " - " + gTestN2;
}

// Listen for cut & copy events
var gCopyCount = 0,
  gCutCount = 0;
document.addEventListener("copy", function () {
  gCopyCount++;
});
document.addEventListener("cut", function () {
  gCutCount++;
});

// Helper methods
function selectNode(aSelector, aCb) {
  var dn = document.querySelector(aSelector);
  var range = document.createRange();
  range.selectNodeContents(dn);
  window.getSelection().removeAllRanges();
  window.getSelection().addRange(range);
  if (aCb) {
    aCb();
  }
}

function selectInputNode(aSelector, aCb) {
  var dn = document.querySelector(aSelector);
  synthesizeMouse(dn, 10, 10, {});
  SimpleTest.executeSoon(function () {
    synthesizeKey("A", { accelKey: true });
    // Clear the user activation state which is set from synthesized mouse and
    // key event.
    SpecialPowers.wrap(document).clearUserGestureActivation();
    SimpleTest.executeSoon(aCb);
  });
}

// Callback functions for attaching to the button
function execCommand(aCommand, aShouldSucceed, aAsync = false) {
  var cb = function (e) {
    e.preventDefault();
    document.removeEventListener("keydown", cb);

    if (aAsync) {
      setTimeout(() => {
        is(
          aShouldSucceed,
          document.execCommand(aCommand),
          "Keydown caused " + aCommand + " invocation" + testLoc()
        );
      }, 0);
    } else {
      is(
        aShouldSucceed,
        document.execCommand(aCommand),
        "Keydown caused " + aCommand + " invocation" + testLoc()
      );
    }
  };
  return cb;
}

// The basic test set. Tries to cut/copy everything
function cutCopyAll(
  aDoCut,
  aDoCopy,
  aDone,
  aNegate,
  aClipOverride,
  aJustClipboardNegate
) {
  var execCommandAlwaysSucceed = !!(aClipOverride || aJustClipboardNegate);

  function waitForClipboard(aCond, aSetup, aNext, aNegateOne) {
    if (aClipOverride) {
      aCond = aClipOverride;
      aNegateOne = false;
    }
    if (aNegate || aNegateOne || aJustClipboardNegate) {
      SimpleTest.waitForClipboard(
        null,
        aSetup,
        aNext,
        aNext,
        "text/plain",
        WATCH_TIMEOUT,
        true
      );
    } else {
      SimpleTest.waitForClipboard(aCond, aSetup, aNext, aNext);
    }
  }

  function validateCutCopy(aExpectedCut, aExpectedCopy) {
    if (aNegate) {
      aExpectedCut = aExpectedCopy = 0;
    } // When we are negating - we always expect callbacks not to be run

    is(
      aExpectedCut,
      gCutCount,
      (aExpectedCut > 0
        ? "Expect cut callback to run"
        : "Expect cut callback not to run") + testLoc()
    );
    is(
      aExpectedCopy,
      gCopyCount,
      (aExpectedCopy > 0
        ? "Expect copy callback to run"
        : "Expect copy callback not to run") + testLoc()
    );
    gCutCount = gCopyCount = 0;
  }

  function step(n) {
    function nextStep() {
      step(n + 1);
    }

    // Reset the user activation state before running next test.
    SpecialPowers.wrap(document).clearUserGestureActivation();

    document.querySelector("span").textContent = "span text";
    document.querySelector("input[type=text]").value = "text text";
    document.querySelector("input[type=password]").value = "password text";
    document.querySelector("textarea").value = "textarea text";

    var contentEditableNode = document.querySelector(
      "div[contentEditable=true]"
    );
    if (contentEditableNode) {
      contentEditableNode.textContent = "contenteditable text";
    }

    gTestN2 = n;
    switch (n) {
      case 0:
        // copy on readonly selection
        selectNode("span");
        waitForClipboard(
          "span text",
          function () {
            aDoCopy(true);
          },
          nextStep
        );
        return;

      case 1:
        validateCutCopy(0, 1);

        // cut on readonly selection
        selectNode("span");

        waitForClipboard(
          "span text",
          function () {
            aDoCut(execCommandAlwaysSucceed);
          },
          nextStep,
          true
        );
        return;

      case 2:
        validateCutCopy(1, 0);

        // copy on textbox selection
        selectInputNode("input[type=text]", nextStep);
        return;

      case 3:
        waitForClipboard(
          "text text",
          function () {
            selectInputNode("input[type=text]", function () {
              aDoCopy(true);
            });
          },
          nextStep
        );
        return;

      case 4:
        validateCutCopy(0, 1);

        // cut on textbox selection
        selectInputNode("input[type=text]", nextStep);
        return;

      case 5:
        waitForClipboard(
          "text text",
          function () {
            aDoCut(true);
          },
          nextStep
        );
        return;

      case 6:
        validateCutCopy(1, 0);

        // copy on password selection
        selectInputNode("input[type=password]", nextStep);
        return;

      case 7:
        waitForClipboard(
          null,
          function () {
            aDoCopy(execCommandAlwaysSucceed);
          },
          nextStep,
          true
        );
        return;

      case 8:
        validateCutCopy(0, 1);

        // cut on password selection
        selectInputNode("input[type=password]", nextStep);
        return;

      case 9:
        waitForClipboard(
          null,
          function () {
            aDoCut(execCommandAlwaysSucceed);
          },
          nextStep,
          true
        );
        return;

      case 10:
        validateCutCopy(1, 0);

        // copy on textarea selection
        selectInputNode("textarea", nextStep);
        return;

      case 11:
        waitForClipboard(
          "textarea text",
          function () {
            aDoCopy(true);
          },
          nextStep
        );
        return;

      case 12:
        validateCutCopy(0, 1);

        // cut on password selection
        selectInputNode("textarea", nextStep);
        return;

      case 13:
        waitForClipboard(
          "textarea text",
          function () {
            aDoCut(true);
          },
          nextStep
        );
        return;

      case 14:
        validateCutCopy(1, 0);

        // copy on no selection
        document.querySelector("textarea").blur();

        waitForClipboard(
          null,
          function () {
            aDoCopy(true);
          },
          nextStep,
          true
        );
        return;

      case 15:
        validateCutCopy(0, 1);

        // cut on no selection
        waitForClipboard(
          null,
          function () {
            aDoCut(execCommandAlwaysSucceed);
          },
          nextStep,
          true
        );
        return;

      case 16:
        validateCutCopy(1, 0);

        if (!document.querySelector("div[contentEditable=true]")) {
          // We're done! (no contentEditable node!)
          step(-1);
          return;
        }
        break;

      case 17:
        // copy on contenteditable selection
        waitForClipboard(
          "contenteditable text",
          function () {
            selectNode("div[contentEditable=true]", function () {
              aDoCopy(true);
            });
          },
          nextStep
        );
        return;

      case 18:
        validateCutCopy(0, 1);
        break;

      case 19:
        // cut on contenteditable selection
        waitForClipboard(
          "contenteditable text",
          function () {
            selectNode("div[contentEditable=true]", function () {
              aDoCut(true);
            });
          },
          nextStep
        );
        return;

      case 20:
        validateCutCopy(1, 0);
        break;

      default:
        aDone();
        return;
    }

    SimpleTest.executeSoon(function () {
      step(n + 1);
    });
  }

  step(0);
}

function allMechanisms(aCb, aClipOverride, aNegateAll) {
  function testStep(n) {
    gTestN1 = n;
    switch (n) {
      /** Test for Bug 1012662 **/
      case 0:
        // Keyboard issued
        cutCopyAll(
          function docut() {
            synthesizeKey("x", { accelKey: true });
          },
          function docopy() {
            synthesizeKey("c", { accelKey: true });
          },
          function done() {
            testStep(n + 1);
          },
          false,
          aClipOverride,
          aNegateAll
        );
        return;

      case 1:
        // Button issued
        cutCopyAll(
          function docut(aSucc) {
            document.addEventListener("keydown", execCommand("cut", aSucc));
            sendString("Q");
          },
          function docopy(aSucc) {
            document.addEventListener("keydown", execCommand("copy", aSucc));
            sendString("Q");
          },
          function done() {
            testStep(n + 1);
          },
          false,
          aClipOverride,
          aNegateAll
        );
        return;

      case 2:
        // Not triggered by user gesture
        cutCopyAll(
          function doCut() {
            is(
              false,
              document.execCommand("cut"),
              "Can't directly execCommand not in a user callback"
            );
          },
          function doCopy() {
            is(
              false,
              document.execCommand("copy"),
              "Can't directly execCommand not in a user callback"
            );
          },
          function done() {
            testStep(n + 1);
          },
          true,
          aClipOverride,
          aNegateAll
        );
        return;

      /** Test for Bug 1597857 **/
      case 3:
        // Button issued async
        cutCopyAll(
          function docut(aSucc) {
            document.addEventListener(
              "keydown",
              execCommand("cut", aSucc, true)
            );
            sendString("Q");
          },
          function docopy(aSucc) {
            document.addEventListener(
              "keydown",
              execCommand("copy", aSucc, true)
            );
            sendString("Q");
          },
          function done() {
            testStep(n + 1);
          },
          false,
          aClipOverride,
          aNegateAll
        );
        return;

      default:
        aCb();
    }
  }
  testStep(0);
}

// Run the tests
SimpleTest.waitForExplicitFinish();
SimpleTest.requestLongerTimeout(5); // On the emulator - this times out occasionally
SimpleTest.waitForFocus(function () {
  function justCancel(aEvent) {
    aEvent.preventDefault();
  }

  function override(aEvent) {
    aEvent.clipboardData.setData("text/plain", "overridden");
    aEvent.preventDefault();
  }

  allMechanisms(function () {
    gTestN0 = 1;
    document.addEventListener("cut", override);
    document.addEventListener("copy", override);

    allMechanisms(function () {
      gTestN0 = 2;
      document.removeEventListener("cut", override);
      document.removeEventListener("copy", override);
      document.addEventListener("cut", justCancel);
      document.addEventListener("copy", justCancel);

      allMechanisms(
        function () {
          SimpleTest.finish();
        },
        null,
        true
      );
    }, "overridden");
  });
});
