var assert = chai.assert;

describe('Text Selection', function() {
  this.timeout(testconfig.timeouts.standard);

  /* Utility functions */

  function setupElement(ele) {
    // A bit of a force-hack to ensure the element is seen as active for the tests.
    com.keyman.dom['DOMEventHandlers'].states._lastActiveElement = ele;
    com.keyman.dom['DOMEventHandlers'].states._activeElement = ele;

    return ele;
  }

  function assertInputSteps(ele, count, setup) {
    return new Promise((resolve, reject) => {
      var i = 0;
      var listener = function() {
        i++;
        if(i == count) {
          ele.removeEventListener("input", listener);
          resolve();
        }
      };
      ele.addEventListener("input", listener);
      setup();
    });
  }

  function instantiateBrowserDriver(ele) {
    return new KMWRecorder.BrowserDriver(setupElement(ele));
  }

  /* Define key event specs */

  var keys = {};
  for (var i = 0; i < 26; i++) {
    var simple = {"type":"key","key":String.fromCharCode(i+97),"code":"Key"+String.fromCharCode(i+65),"keyCode":i+65,"modifierSet":0,"location":0};
    var key = new KMWRecorder.PhysicalInputEventSpec(simple);
    keys[String.fromCharCode(i+65)] = key;
  }
  keys.Backspace = new KMWRecorder.PhysicalInputEventSpec({"type":"key","key":"Backspace","code":"Backspace","keyCode":8,"modifierSet":0,"location":0});

  /* Test Setup */

  // TODO: Add automated tests for editable DIV, designMode iframe
  for(var inputType of ['Input', 'TextArea']) {
    describe('Text Selection in '+inputType, function() {
      before(function() {
        // These tests require use of KMW's device-detection functionality.
        assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure.");
        fixture.setBase('fixtures');
        fixture.load("single"+inputType+".html");

        this.timeout(testconfig.timeouts.scriptLoad*2);
        return setupKMW(null, testconfig.timeouts.scriptLoad).then(() => {
          return loadKeyboardFromJSON("/keyboards/web_context_tests.json", testconfig.timeouts.scriptLoad).then(() => {
            return keyman.setActiveKeyboard("web_context_tests");
          });
        });
      });

      after(function() {
        fixture.cleanup();
        keyman.removeKeyboards('web_context_tests');
        teardownKMW();
      });

      afterEach(function(done) {
        fixture.load("single"+inputType+".html");
        window.setTimeout(function(){
          done();
        }, testconfig.timeouts.eventDelay);
      });

      /*
        --- Tests ---

        A-E: BMP tests
        F-J: SMP tests
        K-O: Mixed BMP+SMP tests (O = SMP)
        Q-Z: Unused
      */

      /**
       * TEST_CONTEXT_BASE
       *
       * Using the web_context_tests keyboard, type abcd. The output after the final key should be '!'.
       */
      it('Should do a basic transform without selection involved', async () => {
        var ele = document.getElementById("singleton");
        var eventDriver = instantiateBrowserDriver(ele);

        await assertInputSteps(ele, 5, () => {
          eventDriver.simulateEvent(keys.E);
          eventDriver.simulateEvent(keys.A);
          eventDriver.simulateEvent(keys.B);
          eventDriver.simulateEvent(keys.C);
          eventDriver.simulateEvent(keys.D);
        });

        assert.strictEqual(ele.value, 'e!', "Expected keys eabcd to transform to e!");
        assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2,2], "Expected caret to be at end of text");
      });

      it('Should do a basic transform without selection involved - SMP', async () => {
        var ele = document.getElementById("singleton");
        var eventDriver = instantiateBrowserDriver(ele);

        await assertInputSteps(ele, 5, () => {
          eventDriver.simulateEvent(keys.J);
          eventDriver.simulateEvent(keys.F);
          eventDriver.simulateEvent(keys.G);
          eventDriver.simulateEvent(keys.H);
          eventDriver.simulateEvent(keys.I);
        });

        assert.strictEqual(ele.value, '𐌈𐌙', "Expected keys eabcd to transform to 𐌈𐌙");
        assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4,4], "Expected caret to be at end of text");
      });

      for(var direction of ['forward', 'backward']) {

        /**
         * TEST_SELECTION
         *
         * Using the web_context_tests keyboard, type abcx. Select bc and type q.
         * The output after the final key should be aqx.
         */
        it('Should do a basic selection replacement, in '+direction+' direction', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'abcx'

          await assertInputSteps(ele, 4, () => {
            eventDriver.simulateEvent(keys.A);
            eventDriver.simulateEvent(keys.B);
            eventDriver.simulateEvent(keys.C);
            eventDriver.simulateEvent(keys.X);
          });

          assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'");

          // Step 2, select 'bc', replace with 'q'

          ele.selectionStart = 1;
          ele.selectionEnd = 3;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 3], "Sanity check: expected selection to be 'bc'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.Q);
          });

          assert.strictEqual(ele.value, 'aqx', "Expected selection to have been replaced with 'q'");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'q'");
        });

        it('Should do a basic selection replacement, with a matching rule, in '+direction+' direction', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'abcx'

          await assertInputSteps(ele, 4, () => {
            eventDriver.simulateEvent(keys.A);
            eventDriver.simulateEvent(keys.B);
            eventDriver.simulateEvent(keys.C);
            eventDriver.simulateEvent(keys.X);
          });

          assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'");

          // Step 2, select 'bc', replace with [p] => 'q'

          ele.selectionStart = 1;
          ele.selectionEnd = 3;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 3], "Sanity check: expected selection to be 'bc'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.P);
          });

          assert.strictEqual(ele.value, 'aqx', "Expected selection to have been replaced with 'q'");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'q'");
        });

        it('Should do a basic selection replacement, in '+direction+' direction - SMP', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'fghj'

          await assertInputSteps(ele, 4, () => {
            eventDriver.simulateEvent(keys.F);
            eventDriver.simulateEvent(keys.G);
            eventDriver.simulateEvent(keys.H);
            eventDriver.simulateEvent(keys.J);
          });

          assert.strictEqual(ele.value, '𐌀𐌁𐌂𐌈', "Sanity check: expected text to be '𐌀𐌁𐌂𐌈'");

          // Step 2, select '𐌁𐌂', replace with '𐍈'

          ele.selectionStart = 2;
          ele.selectionEnd = 6;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 6], "Sanity check: expected selection to be '𐌁𐌂'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.O);
          });

          assert.strictEqual(ele.value, '𐌀𐍈𐌈', "Expected selection to have been replaced with '𐍈'");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4, 4], "Expected caret to be after '𐍈'");
        });

        /**
         * TEST_CONTEXT_SELECTION
         *
         * Using the attached web_context_tests keyboard, type abcx. Select the x character, and type d.
         * The output after the final key should be 'abcd'.
         */
        it('Should ignore context when a selection, in '+direction+' direction, is made', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'abcx'

          await assertInputSteps(ele, 4, () => {
            eventDriver.simulateEvent(keys.A);
            eventDriver.simulateEvent(keys.B);
            eventDriver.simulateEvent(keys.C);
            eventDriver.simulateEvent(keys.X);
          });

          assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'");

          // Step 2, select 'x', replace with 'd'

          ele.selectionStart = 3;
          ele.selectionEnd = 4;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 4], "Sanity check: expected selection to be 'x'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.D);
          });

          assert.strictEqual(ele.value, 'abcd', "Expected selection to have been replaced with 'd'");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4, 4], "Expected caret to be after 'd'");
        });

        it('Should ignore context when a selection, in '+direction+' direction, is made - SMP', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'fghj'

          await assertInputSteps(ele, 4, () => {
            eventDriver.simulateEvent(keys.F);
            eventDriver.simulateEvent(keys.G);
            eventDriver.simulateEvent(keys.H);
            eventDriver.simulateEvent(keys.J);
          });

          assert.strictEqual(ele.value, '𐌀𐌁𐌂𐌈', "Sanity check: expected text to be '𐌀𐌁𐌂𐌈'");

          // Step 2, select '𐌈', replace with [i]

          ele.selectionStart = 6;
          ele.selectionEnd = 8;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 8], "Sanity check: expected selection to be '𐌈'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.I);
          });

          assert.strictEqual(ele.value, '𐌀𐌁𐌂i', "Expected selection to have been replaced with 'i'");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [7, 7], "Expected caret to be after 'i'");
        });

        /**
         * TEST_CONTEXT_SELECTION_2
         *
         * Using the attached web_context_tests keyboard, type abcx. Select the x character, and
         * delete it with Backspace. Type d. The output after the final key should be !.
         */
        it('Should correctly delete selection, in '+direction+' direction, with backspace and not lose sync', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'abcx'

          await assertInputSteps(ele, 4, () => {
            eventDriver.simulateEvent(keys.A);
            eventDriver.simulateEvent(keys.B);
            eventDriver.simulateEvent(keys.C);
            eventDriver.simulateEvent(keys.X);
          });

          assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'");

          // Step 2, select 'x', press Backspace

          ele.selectionStart = 3;
          ele.selectionEnd = 4;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 4], "Sanity check: expected selection to be 'x'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.Backspace);
          });


          assert.strictEqual(ele.value, 'abc', "Expected selection to have been deleted");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 3], "Expected caret to be after 'c'");

          // Step 3, type [d]

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.D);
          });

          assert.strictEqual(ele.value, '!', "Expected text to have been transformed to '!'");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 1], "Expected caret to be after '!'");
        });

        it('Should correctly delete selection, in '+direction+' direction, with backspace and not lose sync - SMP', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'abcx'

          await assertInputSteps(ele, 4, () => {
            eventDriver.simulateEvent(keys.F);
            eventDriver.simulateEvent(keys.G);
            eventDriver.simulateEvent(keys.H);
            eventDriver.simulateEvent(keys.J);
          });

          assert.strictEqual(ele.value, '𐌀𐌁𐌂𐌈', "Sanity check: expected text to be '𐌀𐌁𐌂𐌈'");

          // Step 2, select '𐌈', press Backspace

          ele.selectionStart = 6;
          ele.selectionEnd = 8;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 8], "Sanity check: expected selection to be '𐌈'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.Backspace);
          });


          assert.strictEqual(ele.value, '𐌀𐌁𐌂', "Expected selection to have been deleted");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 6], "Expected caret to be after '𐌂'");

          // Step 3, type [i]

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.I);
          });

          assert.strictEqual(ele.value, '𐌙', "Expected text to have been transformed to '𐌙'");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after '𐌙'");
        });

        /**
         * TEST_CONTEXT_SELECTION_3
         *
         * Using the attached web_context_tests keyboard, type abcx. Select the x character, and type y.
         * Press Backspace, and type d. The output after the final key should be !.
         */
        it('Should correctly replace selection, in '+direction+' direction, and not lose sync', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'abcx'

          await assertInputSteps(ele, 4, () => {
            eventDriver.simulateEvent(keys.A);
            eventDriver.simulateEvent(keys.B);
            eventDriver.simulateEvent(keys.C);
            eventDriver.simulateEvent(keys.X);
          });

          assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'");

          // Step 2, select 'x', press y

          ele.selectionStart = 3;
          ele.selectionEnd = 4;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 4], "Sanity check: expected selection to be 'x'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.Y);
          });

          assert.strictEqual(ele.value, 'abcy', "Expected selection to have been replaced with y");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4, 4], "Expected caret to be after 'y'");

          // Step 3, press Backspace

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.Backspace);
          });

          assert.strictEqual(ele.value, 'abc', "Expected y to have been deleted");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 3], "Expected caret to be after 'c'");

          // Step 4, type 'd'

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.D);
          });

          assert.strictEqual(ele.value, '!', "Expected text to have been transformed to '!'");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 1], "Expected caret to be after '!'");
        });

        it('Should correctly replace selection, in '+direction+' direction, and not lose sync - SMP', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'abcx'

          await assertInputSteps(ele, 4, () => {
            eventDriver.simulateEvent(keys.F);
            eventDriver.simulateEvent(keys.G);
            eventDriver.simulateEvent(keys.H);
            eventDriver.simulateEvent(keys.J);
          });

          assert.strictEqual(ele.value, '𐌀𐌁𐌂𐌈', "Sanity check: expected text to be '𐌀𐌁𐌂𐌈'");

          // Step 2, select '𐌈', press [k]

          ele.selectionStart = 6;
          ele.selectionEnd = 8;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 8], "Sanity check: expected selection to be '𐌈'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.K);
          });

          assert.strictEqual(ele.value, '𐌀𐌁𐌂𐌰', "Expected selection to have been replaced with 𐌰");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [8, 8], "Expected caret to be after '𐌰'");

          // Step 3, press Backspace

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.Backspace);
          });

          assert.strictEqual(ele.value, '𐌀𐌁𐌂', "Expected 𐌰 to have been deleted");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 6], "Expected caret to be after '𐌂'");

          // Step 4, type [i]

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.I);
          });

          assert.strictEqual(ele.value, '𐌙', "Expected text to have been transformed to '𐌙'");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after '𐌙'");
        });

        /**
         * TEST_CONTEXT_SELECTION_4
         *
         * Using the attached web_context_tests keyboard, type xabcx.
         * Select the abc characters, and type d. The output after the final key should be xdx.
         */
        it('Should not treat the selection, in '+direction+' direction, as context', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: 'xabcx'

          await assertInputSteps(ele, 5, () => {
            eventDriver.simulateEvent(keys.X);
            eventDriver.simulateEvent(keys.A);
            eventDriver.simulateEvent(keys.B);
            eventDriver.simulateEvent(keys.C);
            eventDriver.simulateEvent(keys.X);
          });

          assert.strictEqual(ele.value, 'xabcx', "Sanity check: expected text to be 'xabcx'");

          // Step 2, select 'abc', press 'd'

          ele.selectionStart = 1;
          ele.selectionEnd = 4;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 4], "Sanity check: expected selection to be 'abc'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.D);
          });

          assert.strictEqual(ele.value, 'xdx', "Expected selection to have been replaced with d");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'd'");
        });

        it('Should not treat the selection, in '+direction+' direction, as context - SMP', async () => {
          var ele = document.getElementById("singleton");
          var eventDriver = instantiateBrowserDriver(ele);

          // Step 1: [k][f][g][h][k]

          await assertInputSteps(ele, 5, () => {
            eventDriver.simulateEvent(keys.K);
            eventDriver.simulateEvent(keys.F);
            eventDriver.simulateEvent(keys.G);
            eventDriver.simulateEvent(keys.H);
            eventDriver.simulateEvent(keys.K);
          });

          assert.strictEqual(ele.value, '𐌰𐌀𐌁𐌂𐌰', "Sanity check: expected text to be '𐌰𐌀𐌁𐌂𐌰'");

          // Step 2, select '𐌀𐌁𐌂', press [i]

          ele.selectionStart = 2;
          ele.selectionEnd = 8;
          ele.selectionDirection = direction;
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 8], "Sanity check: expected selection to be '𐌀𐌁𐌂'");

          await assertInputSteps(ele, 1, () => {
            eventDriver.simulateEvent(keys.I);
          });

          assert.strictEqual(ele.value, '𐌰i𐌰', "Expected selection to have been replaced with i");
          assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 3], "Expected caret to be after 'i'");
        });
      }
    });
  }
});
