var assert = require('chai').assert;
let fs = require('fs');
let vm = require('vm');

let KeyboardProcessor = require('../../../build/index.bundled.js');

// Required initialization setup.
global.com = KeyboardProcessor.com; // exports all keyboard-processor namespacing.

let KMWRecorder = require('../../../../recorder/build/nodeProctor');

/*
 * ABOUT THIS TEST SUITE
 * ---------------------
 *
 * This suite contains two types of tests, both designed to test all possible variations
 * of behaviors that `KeyboardInterface.fullContextMatch` may be expected to handle.
 *
 * Type 1:  White-box tests for validity of the generated context-cache
 * - uses only the `baseSequence` of each test spec definition; does not
 *   use any `fullMatchDefs` entries.
 * - uses the specified `contextCache` entry of each test spec in assertions
 * - CTRL+F `Tests "stage 1" of fullContextMatch` for more details.
 *
 * Type 2:  Black-box rule-matching tests
 * - uses both `baseSequence` and `fullMatchDefs` entries of a test spec
 * - tests that each simulation sequence's output either passes or fails against
 *   the specified rule, asserting against the specified `result` value.
 *   - Currently, `result` is always `true` for each spec's `baseSequence`.
 *     It should be a removable limitation, though.
 */

let device = {
  formFactor: 'desktop',
  OS: 'windows',
  browser: 'native'
}

var keyboard;

var toSupplementaryPairString = function(code){
  var H = Math.floor((code - 0x10000) / 0x400) + 0xD800;
  var L = (code - 0x10000) % 0x400 + 0xDC00;

  return String.fromCharCode(H, L);
}

function runEngineRuleSet(ruleSet, defaultNoun) {
  defaultNoun = defaultNoun ? defaultNoun : "Rule";

  for(var i = 0; i < ruleSet.length; i++) {
    var ruleDef = ruleSet[i]; // for example, DEADKEY_TEST_1.

    var matchDefs = [{
        sequence: ruleDef.baseSequence,
        result: true,
        msg: "Rule " + ruleDef.id + ":  basic application of rule failed."}
      ].concat(ruleDef.fullMatchDefs ? ruleDef.fullMatchDefs : []);

    for(var j = 0; j < matchDefs.length; j++) {
      // Prepare the context!
      var matchTest = matchDefs[j];
      var ruleSeq = new KMWRecorder.RecordedKeystrokeSequence(matchTest.sequence);
      let proctor = new KMWRecorder.NodeProctor(keyboard, device, assert.equal);

      // We want to specify the OutputTarget for this test; our actual concern is the resulting context.
      var target = new com.keyman.text.Mock();
      ruleSeq.test(proctor, target);

      // Now for the real test!
      let processor = new KeyboardProcessor(device);
      processor.activeKeyboard = keyboard;
      var res = processor.keyboardInterface.fullContextMatch(ruleDef.n, target, ruleDef.rule);

      var msg = matchTest.msg;
      if(!msg) {
        msg = defaultNoun + " incorrectly reported as " + (matchTest.result ? "unmatched!" : "matched!");
      }
      assert.equal(res, matchTest.result, msg);
    }
  }
}

// Unfortunately, at present, this is all handwritten stuff crafted from partial Recorder use.  Might should make
// a page that writes this format instead.  Note that I've omitted the eventSpec part here, since these tests are
// Node-only.

/*
 *  Start definition of isolated rule tests for validity of `fullContextMatch` (KFCM) components.
 */

 /* Keyman language equivalent:
 *
 * dk(1) > 'success'
 */
var DEADKEY_TEST_1 = {
  id: 1,
  // Match condition for rule
  rule: [{t:'d', d: 1}],
  // Start of context relative to cursor
  n: 1,
  ln: 1,
  // Resulting context map
  contextCache: [1],
  baseSequence: { "output": "", "inputs": [
    {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}
  ]},
  fullMatchDefs: [{
    sequence: { "output": "", "inputs": [
      // Does it fail with a different deadkey in the position?
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}
    ]},
    result: false,
    msg: "Rule 1:  did not fail when incorrect deadkey was present."
  }, {
    sequence: { "output": "", "inputs": [
      // Slightly out-of-context deadkey shouldn't affect the match.
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true},
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}
    ]},
    result: true,
    msg: "Rule 1:  failed when extra deadkey context exists in history."
  }, {
    sequence: { "output": "", "inputs": [
      // Slightly out-of-context deadkey shouldn't affect the match.
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true},
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}
    ]},
    result: false,
    msg: "Rule 1:  did not fail upon incorrect deadkey ordering at top of context."
  }]
};

/* Keyman language equivalent:
 *
 * 'a' dk(0) dk(1) 'b' > 'success'
 */
var DEADKEY_TEST_2 = {
  id: 2,
  // Match condition for rule
  rule: ['a', {t:'d', d: 0}, {t:'d', d: 1}, 'b'],
  // Start of context relative to cursor
  n: 5,
  ln: 4,
  // Resulting context map
  contextCache: ['a', 0, 1, 'b'],
  baseSequence: { "output": "abc", "inputs": [
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    // The test has an extra character appended that's not part of the check.
    {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //c
  ]},
  fullMatchDefs: [{
    sequence: { "output": "abc", "inputs": [
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true},
      // Should fail with inverted deadkey ordering at same KC_ position.
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true},
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true},
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true},
      // The test has an extra character appended that's not part of the check.
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}
    ]},
    result: false,
    msg: "Rule 2:  did not fail when deadkey ordering was inverted."
  }, {
    sequence: { "output": "cabc", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      // The test has an extra character appended that's not part of the check.
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //c
    ]},
    result: true,
    msg: "Rule 2:  failed when extra character context exists in history."
  }, {
    sequence: { "output": "ab", "inputs": [
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      // The test has an extra deadkey appended that's not part of the check.
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //1
    ]},
    result: true,
    msg: "Rule 2:  out-of-context deadkey caused rule match failure."
  }, {
    sequence: { "output": "abc", "inputs": [
      // Should not fail with a deadkey prepended to the context.
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      // The test has an extra character appended that's not part of the check.
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //c
    ]},
    result: true,
    msg: "Rule 2:  prepended (out of context) deadkey caused rule match failure."
  }]
};

/* Keyman language equivalent:
 *
 * dk(0) 'a' dk(0) dk(0) 'b' > 'success'
 */
var DEADKEY_TEST_3 = {
  id: 3,
  // Match condition for rule
  rule: [{t:'d', d: 0}, 'a', {t:'d', d: 0}, {t:'d', d: 0}, 'b'],
  // Start of context relative to cursor
  n: 5,
  ln: 5,
  // Resulting context map
  contextCache: [0, 'a', 0, 0, 'b'],
  baseSequence: { "output": "ab", "inputs": [
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
  ]},
  fullMatchDefs: [{
    sequence: { "output": "ab", "inputs": [
      // Omission of the first deadkey should result in failure.
      //{"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: false,
    msg: "Rule 3:  required prepended deadkey omission did not prevent a rule match."
  }, {
    sequence: { "output": "ab", "inputs": [
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      // Omission of the one of the duplicated deadkeys should result in failure.
      //{"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //b
    ]},
    result: false,
    msg: "Rule 3:  omitting one copy of a required duplicated deadkey did not prevent a rule match."
  }, {
    sequence: { "output": "ab", "inputs": [
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      // Triplifying deadkeys in the center should result in failure.
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //b
    ]},
    result: false,
    msg: "Rule 3:  triple matching deadkeys where only two required did not prevent a rule match."
  }, {
    sequence: { "output": "ab", "inputs": [
      // Should not fail with a duplicate deadkey prepended to the context.
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: true,
    msg: "Rule 3:  duplicate prepended deadkey prevented a rule match when only one was required."
  }, {
    sequence: { "output": "ab", "inputs": [
      // Should not fail with an unrelated deadkey prepended to the context before the required one.
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: true,
    msg: "Rule 3:  prepended deadkey placed before rule's deadkey prevented a rule match."
  }, {
    sequence: { "output": "ab", "inputs": [
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      // Should fail when unrelated, prepended deadkey actually comes after the required one.
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: false,
    msg: "Rule 3:  prepended deadkey after rule's required deadkey failed to prevent a rule match."
  }]
};

/* Keyman language equivalent:
 *
 * 'a' dk(0) dk(0) 'b' dk(0) > 'success'
 */
var DEADKEY_TEST_4 = {
  id: 4,
  // Match condition for rule
  rule: ['a', {t:'d', d: 0}, {t:'d', d: 0}, 'b', {t:'d', d: 0}],
  // Start of context relative to cursor
  n: 6,
  ln: 5,
  // Resulting context map
  contextCache: ['a', 0, 0, 'b', 0],

  baseSequence: { "output": "abc", "inputs": [
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    // The test has an extra character appended that's not part of the check.
    {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //c
  ]}
  // No specialized fullMatchDefs here, as any appended deadkeys are automatically 'in context' for rules.
};

/* Keyman language equivalent:
 *
 * 'a' 'b' 'b' 'a' > 'success'
 */
var DEADKEY_TEST_5 = {
  id: 5,
  // Match condition for rule
  rule: ['a', 'b', 'b', 'a'],
  // Start of context relative to cursor
  n: 5,
  ln: 4,
  // Resulting context map
  contextCache: ['a', 'b', 'b', 'a'],

  baseSequence: { "output": "abbac", "inputs": [
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    // The test has an extra character appended that's not part of the check.
    {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //c
  ]}
};

/* Keyman language equivalent:
 *
 * dk(1) dk(2) dk(0) dk(1) dk(2) > 'success'
 */
var DEADKEY_TEST_6 = {
  id: 6,
  // Match condition for rule
  rule: [{t:'d', d: 1}, {t:'d', d: 2}, {t:'d', d: 0}, {t:'d', d: 1}, {t:'d', d: 2}],
  // Start of context relative to cursor
  n: 5,
  ln: 5,
  // Resulting context map
  contextCache: [1, 2, 0, 1, 2],

  baseSequence: { "output": "", "inputs": [
    // Testing with an extra deadkey at the start.
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
    {"type":"key","keyCode":51,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //3
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
    {"type":"key","keyCode":51,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //3
  ]}
};

/* Keyman language equivalent:
 *
 * 'c' 'a' 'b' context(3) context(2) > 'success'
 */
var ANY_CONTEXT_TEST_1 = {
  id: 1,
  // Match condition for rule
  rule: ['c', "a", "b", {t:'c', c:3}, {t:'c', c:2}],
  // Start of context relative to cursor
  n: 5,
  ln: 5,
  // Resulting context map
  contextCache: ['c', 'a', 'b', 'b', 'a'],

  baseSequence: { "output": "cabba", "inputs": [
    {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "cacca", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 1: Plain text mismatch with successful context() statements is matching the rule."
  }, {
    sequence: { "output": "cabaa", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 1: mismatched context() rule component is not failing the rule."
  }]
};

/* Keyman language equivalent:
 *
 * store(bc) 'bc'
 * 'c' 'a' any(bc) context(3) 'a' > 'success'
 */
var ANY_CONTEXT_TEST_2 = {
  id: 2,
  // Match condition for rule
  rule: ['c', 'a', {t:'a', a: "bc"}, {t:'c', c:3}, 'a'],
  // Start of context relative to cursor
  n: 5,
  ln: 5,
  // Resulting context map
  contextCache: ['c', 'a', 'b', 'b', 'a'],

  baseSequence: { "output": "cabba", "inputs": [
    {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "cacca", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: true,
    msg: "Rule 2: Alternate 'any' store character failed to match the rule."
  }, {
    sequence: { "output": "cabca", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 2: 'any' and 'context' correspond to mismatching characters, but matched the rule."
  }]
};

/* Keyman language equivalent:
 *
 * store(ac) 'ac'
 * store(bc) 'bc'
 *
 * 'c' any(ac) any(bc) context(3) context(2) > 'success'
 */
var ANY_CONTEXT_TEST_3 = {
  id: 3,
  // Match condition for rule
  rule: ['c', {t:'a', a: "ac"}, {t:'a', a: "bc"}, {t:'c', c:3}, {t:'c', c:2}],
  // Start of context relative to cursor
  n: 5,
  ln: 5,
  // Resulting context map
  contextCache: ['c', 'a', 'b', 'b', 'a'],

  baseSequence: { "output": "cabba", "inputs": [
    {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "cacca", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: true,
    msg: "Rule 3: Alternate 'any' store character failed to match the rule."
  }, {
    sequence: { "output": "ccccc", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //c
    ]},
    result: true,
    msg: "Rule 3: Alternate 'any' store character failed to match the rule."
  }, {
    sequence: { "output": "cabab", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: false,
    msg: "Rule 3: context() rule component is matching the incorrect any() component."
  }]
};

/* Keyman language equivalent:
 *
 * store(ab) 'ab'
 * store(bc) 'bc'
 *
 * 'c' any(ab) index(bc, 2) 'a' > 'success'
 */
var ANY_INDEX_TEST_1 = {
  id: 1,
  // Match condition for rule
  rule: ['c', 'a', {t:'a', a: "bc"}, {t:'i', i:"bc", o:3}, 'a'],
  // Start of context relative to cursor
  n: 5,
  ln: 5,
  // Resulting context map
  contextCache: ['c', 'a', 'b', 'b', 'a'],

  baseSequence: { "output": "cabba", "inputs": [
    {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "cacca", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: true,
    msg: "Rule 1: Alternate 'any' store character failed to match the rule."
  }, {
    sequence: { "output": "cabca", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 1: 'any' and 'output' correspond to mismatching characters, but matched the rule."
  }]
};

/* Keyman language equivalent:
 *
 * store(ab) 'ab'
 * store(bc) 'bc'
 *
 * 'c' any(ab) index(bc, 2) index(bc, 2) index(ab, 2) > 'success'
 */
var ANY_INDEX_TEST_2 = {
  id: 2,
  // Match condition for rule
  rule: ['c', {t:'a', a:"ab"}, {t:'i', i:"bc", o:2}, {t:'i', i:"bc", o:2}, {t:'i', i:"ab", o:2}],
  // Start of context relative to cursor
  n: 5,
  ln: 5,
  // Resulting context map
  contextCache: ['c', 'a', 'b', 'b', 'a'],

  baseSequence: { "output": "cabba", "inputs": [
    {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "cacca", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 2: Mismatch with secondary output store did not fail the rule."
  }, {
    sequence: { "output": "cbccb", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: true,
    msg: "Rule 2: Alternate 'any' store character failed to match the rule."
  }]
};

/* Keyman language equivalent:
 *
 * store(ab) 'ab'
 * store(bc) 'bc'
 *
 * 'c' any(ab) any(bc) index(bc, 3) index(ab, 2) > 'success'
 */
var ANY_INDEX_TEST_3 = {
  id: 3,
  // Match condition for rule
  rule: ['c', {t:'a', a:"ab"}, {t:'a', a:"bc"}, {t:'i', i:"bc", o:3}, {t:'i', i:"ab", o:2}],
  // Start of context relative to cursor
  n: 5,
  ln: 5,
  // Resulting context map
  contextCache: ['c', 'a', 'b', 'b', 'a'],

  baseSequence: { "output": "cabba", "inputs": [
    {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "cacca", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: true,
    msg: "Rule 3a: Error with index() when a rule has multiple any() checks."
  }, {
    sequence: { "output": "cbccb", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: true,
    msg: "Rule 3b: Error with index() when a rule has multiple any() checks."
  }, {
    sequence: { "output": "cbbbb", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: true,
    msg: "Rule 3c: Error with index() when a rule has multiple any() checks."
  }, {
    sequence: { "output": "cbccc", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //c
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //c
    ]},
    result: false,
    msg: "Rule 3: index() rule check matched the incorrect any()."
  }]
};

/* Keyman language equivalent:
 *
 * store(match) dk(0) dk(1) dk(2)
 * any(match) > 'success'
 */
var DEADKEY_STORE_TEST_1 = {
  id: 1,
  // Match condition for rule
  rule: [{t:'a',a:[{d:0},{d:1},{d:2}]}],
  // Start of context relative to cursor
  n: 1,
  ln: 1,
  // Resulting context map
  contextCache: [0],

  baseSequence: { "output": "", "inputs": [
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //1
  ]},
  fullMatchDefs: [{
    sequence: { "output": "", "inputs": [
      {"type":"key","keyCode":51,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //3
    ]},
    result: true,
    msg: "Rule 1: Alternate 'any' store deadkey character failed to match the rule."
  }, {
    sequence: { "output": "c", "inputs": [
      {"type":"key","keyCode":67,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //c
    ]},
    result: false,
    msg: "Rule 1: standard character mysteriously matched a deadkey from a store."
  }]
};

/* Keyman language equivalent:
 *
 * store(match) dk(0) 'b' dk(2)
 * any(match) > 'success'
 */
var DEADKEY_STORE_TEST_2 = {
  id: 2,
  // Match condition for rule
  rule: [{t:'a',a:[{d:0},'b',{d:2}]}],
  // Start of context relative to cursor
  n: 1,
  ln: 1,
  // Resulting context map
  contextCache: [0],

  baseSequence: { "output": "", "inputs": [
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //1
  ]},
  fullMatchDefs: [{
    sequence: { "output": "", "inputs": [
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //2
    ]},
    result: false,
    msg: "Rule 2: deadkey not in store mysteriously matched within an any(store) op."
  }, {
    sequence: { "output": "b", "inputs": [
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //b
    ]},
    result: true,
    msg: "Rule 2: standard character in store failed to match within an any(store) op."
  }]
};

/* Keyman language equivalent:
 *
 * store(match) dk(0) dk(1) dk(2)
 * store(abc) 'abc'
 *
 * any(match) index(abc, 1) > 'success'
 */
var DEADKEY_STORE_TEST_3 = {
  id: 3,
  // Match condition for rule
  rule: [{t:'a',a:[{d:0},{d:1},{d:2}]},{t:'i',i:"abc", o:1}],
  // Start of context relative to cursor
  n: 2,
  ln: 2,
  // Resulting context map
  contextCache: [0,'a'],

  baseSequence: { "output": "a", "inputs": [
    {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "a", "inputs": [
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 3a: index in deadkey store not properly tracked by indexOutput."
  }, {
    sequence: { "output": "b", "inputs": [
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: true,
    msg: "Rule 3a: index in deadkey store not properly tracked by indexOutput."
  }, {
    sequence: { "output": "a", "inputs": [
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //2
    ]},
    result: false,
    msg: "Rule 3: incorrectly matched rule when deadkey and character were incorrectly ordered."
  }, {
    sequence: { "output": "bb", "inputs": [
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: false,
    msg: "Rule 3: triggered when rule is one regular character out of context."
  }, {
    sequence: { "output": "b", "inputs": [
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //2
    ]},
    result: false,
    msg: "Rule 3: triggered when rule is one deadkey out of context."
  }]
};

/* Keyman language equivalent:
 *
 * store(match) dk(0) dk(1) dk(2)
 * any(match) context(1) > 'success'
 */
var DEADKEY_STORE_TEST_4 = {
  id: 4,
  // Match condition for rule
  rule: [{t:'a',a:[{d:0},{d:1},{d:2}]},{t:'c',c:1}],
  // Start of context relative to cursor
  n: 2,
  ln: 2,
  // Resulting context map
  contextCache: [1,1],

  baseSequence: { "output": "", "inputs": [
    {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
    {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //2
  ]},
  fullMatchDefs: [{
    sequence: { "output": "", "inputs": [
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //2
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //1
    ]},
    result: false,
    msg: "Rule 4: context matched to alternate (not selected) any(store) option erroneously."
  }, {
    sequence: { "output": "b", "inputs": [
      {"type":"key","keyCode":51,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //3
      {"type":"key","keyCode":51,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //3
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //b
    ]},
    result: false,
    msg: "Rule 4: triggered when rule is slightly out of context."
  }]
};

/* Keyman language equivalent:
 *
 * store(match) 'abc'
 * notany(match) any(match) > 'success'
 */
var NOTANY_NUL_TEST_1 = {
  id: 1,
  // Match condition for rule
  rule: [{t: 'a', a:['a','b','c'], n: 1},{t:'a',a:['a', 'b', 'c']}],
  // Start of context relative to cursor
  n: 2,
  ln: 2,
  // Resulting context map
  contextCache: [1, 'a'],

  baseSequence: { "output": "a", "inputs": [
    {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "aa", "inputs": [
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 1: did not fail notany on matched character"
  }, {
    sequence: { "output": "a", "inputs": [
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 1: did not fail notany on nul context"
  }]
};

/* Keyman language equivalent:
 *
 * store(match) dk(0) dk(1) dk(2)
 * store(abc) 'abc'
 *
 * notany(match) any(abc) > 'success'
 */
var NOTANY_NUL_TEST_2 = {
  id: 2,
  // Match condition for rule
  rule: [{t: 'a', a:[{d:0},{d:1},{d:2}], n: 1},{t:'a',a:['a', 'b', 'c']}],
  // Start of context relative to cursor
  n: 2,
  ln: 2,
  // Resulting context map
  contextCache: ['a', 'a'],

  baseSequence: { "output": "aa", "inputs": [
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "a", "inputs": [
      {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 2: did not fail notany on matched deadkey"
  }, {
    sequence: { "output": "a", "inputs": [
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 2: did not fail notany on nul context"
  }]
};

/* Keyman language equivalent:
 *
 * store(first) dk(0) 'b' dk(2)
 * store(second) 'a' dk(1) 'c'
 *
 * notany(first) any(second) > 'success'
 */
var NOTANY_NUL_TEST_3 = {
  id: 3,
  // Match condition for rule
  rule: [{t: 'a', a:[{d:0},'b',{d:2}], n: 1},{t:'a',a:['a', 'b', 'c']}],
  // Start of context relative to cursor
  n: 2,
  ln: 2,
  // Resulting context map
  contextCache: [1, 'a'],

  baseSequence: { "output": "a", "inputs": [
    {"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
    {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
  ]},
  fullMatchDefs: [{
    sequence: { "output": "a", "inputs": [
      {"type":"key","keyCode":49,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //0
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 3: did not properly match a deadkey within a mixed notany store"
  }, {
    sequence: { "output": "ba", "inputs": [
      {"type":"key","keyCode":66,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //b
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 3: did not properly match a character within a mixed notany store"
  }, {
    sequence: { "output": "aa", "inputs": [
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: true,
    msg: "Rule 3: context incorrectly matched a character within a mixed notany store"
  }, {
    sequence: { "output": "a", "inputs": [
      {"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}  //a
    ]},
    result: false,
    msg: "Rule 3: did not fail notany on nul context"
  }]
};

var DEADKEY_RULE_SET = [ DEADKEY_TEST_1, DEADKEY_TEST_2, DEADKEY_TEST_3, DEADKEY_TEST_4,
  DEADKEY_TEST_5, DEADKEY_TEST_6
];
var ANY_CONTEXT_RULE_SET = [ ANY_CONTEXT_TEST_1, ANY_CONTEXT_TEST_2, ANY_CONTEXT_TEST_3 ];
var ANY_INDEX_RULE_SET = [ ANY_INDEX_TEST_1, ANY_INDEX_TEST_2, ANY_INDEX_TEST_3 ];
var DEADKEY_STORE_RULE_SET = [ DEADKEY_STORE_TEST_1, DEADKEY_STORE_TEST_2, DEADKEY_STORE_TEST_3,
   DEADKEY_STORE_TEST_4 ];

var NOTANY_NUL_RULE_SET = [ NOTANY_NUL_TEST_1, NOTANY_NUL_TEST_2, NOTANY_NUL_TEST_3 ];

 var FULL_RULE_SET = [].concat(DEADKEY_RULE_SET, ANY_CONTEXT_RULE_SET, ANY_INDEX_RULE_SET,
   DEADKEY_STORE_RULE_SET, NOTANY_NUL_RULE_SET);

  // -----------

describe('Engine - Context Matching', function() {
  before(function() {
    let kp = new KeyboardProcessor(device);

    // These two lines will load a keyboard from its file; headless-mode `registerKeyboard` will
    // automatically set the keyboard as active.
    var script = new vm.Script(fs.readFileSync('../../test/resources/keyboards/test_simple_deadkeys.js'));
    script.runInThisContext();

    keyboard = kp.activeKeyboard;
  });

  // Tests "stage 1" of fullContextMatch - ensuring that a proper context index map is built.
  it('properly generates extended context data needed by context-matching checks below', function() {
    let matchDefs = FULL_RULE_SET;

    for(var j = 0; j < matchDefs.length; j++) {
      // Prepare the context!
      var ruleDef = matchDefs[j];
      var ruleSeq = new KMWRecorder.RecordedKeystrokeSequence(ruleDef.baseSequence);
      let proctor = new KMWRecorder.NodeProctor(keyboard, device, assert.equal);

      // We want to specify the OutputTarget for this test; our actual concern is the resulting context.
      var target = new com.keyman.text.Mock();
      ruleSeq.test(proctor, target);

      // Now for the real test!
      let processor = new KeyboardProcessor(device);
      processor.activeKeyboard = keyboard;
      var res = processor.keyboardInterface._BuildExtendedContext(ruleDef.n, ruleDef.ln, target);

      assert.sameOrderedMembers(res.valContext, ruleDef.contextCache);
    }
  });

  describe('handles simple deadkey contexts', function() {
    it('for the most basic cases:  DEADKEY_TEST_1', function() {
      runEngineRuleSet([DEADKEY_TEST_1], "Deadkeys");
    });

    it('with deadkey ordering:  DEADKEY_TEST_2', function() {
      runEngineRuleSet([DEADKEY_TEST_2], "Deadkeys");
    });

    it('with repeated deadkeys:  DEADKEY_TEST_3', function() {
      runEngineRuleSet([DEADKEY_TEST_3], "Deadkeys");
    });

    it('with caret after deadkey:  DEADKEY_TEST_4', function() {
      runEngineRuleSet([DEADKEY_TEST_4], "Deadkeys");
    });

    it('with no deadkeys, mid-text:  DEADKEY_TEST_5', function() {
      runEngineRuleSet([DEADKEY_TEST_5], "Deadkeys");
    });

    it('with long sequence of only deadkeys:  DEADKEY_TEST_6', function() {
      runEngineRuleSet([DEADKEY_TEST_6], "Deadkeys");
    });
  });

  describe('handles any + context rule interactions', function() {
    it('for basic context matches, no any:  ANY_CONTEXT_TEST_1', function() {
      runEngineRuleSet([ANY_CONTEXT_TEST_1]);
    });

    it('for any + context-of-any matches:  ANY_CONTEXT_TEST_2', function() {
      runEngineRuleSet([ANY_CONTEXT_TEST_2]);
    });

    it('for rules with multiple any + context-of-any pairs:  ANY_CONTEXT_TEST_3', function() {
      runEngineRuleSet([ANY_CONTEXT_TEST_3]);
    });
  });

  describe('handles any + index context interactions', function() {
    it('for basic any + index matches in context:  ANY_INDEX_TEST_1', function() {
      runEngineRuleSet([ANY_INDEX_TEST_1]);
    });

    it('for one-any to many-index matches in context:  ANY_INDEX_TEST_2', function() {
      runEngineRuleSet([ANY_INDEX_TEST_2]);
    });

    it('for two any + index pairs in rule:  ANY_INDEX_TEST_3', function() {
      runEngineRuleSet([ANY_INDEX_TEST_3]);
    });
  });

  describe('handles interactions with deadkeys in stores', function() {
    it('for any on pure deadkey store:  DEADKEY_STORE_TEST_1', function() {
      runEngineRuleSet([DEADKEY_STORE_TEST_1]);
    });

    it('for any on mixed char+deadkey store:  DEADKEY_STORE_TEST_2', function() {
      runEngineRuleSet([DEADKEY_STORE_TEST_2]);
    });

    it('for index matching any on pure deadkey store:  DEADKEY_STORE_TEST_3', function() {
      runEngineRuleSet([DEADKEY_STORE_TEST_3]);
    });

    it('for context matching any on pure deadkey store:  DEADKEY_STORE_TEST_4', function() {
      runEngineRuleSet([DEADKEY_STORE_TEST_4]);
    });
  });

  describe('handles interactions between notany and nul in context', function() {
    it('with notany against a store with pure characters:  NOTANY_NUL_TEST_1', function() {
      runEngineRuleSet([NOTANY_NUL_TEST_1]);
    });

    it('with notany against a store with pure deadkeys:  NOTANY_NUL_TEST_2', function() {
      runEngineRuleSet([NOTANY_NUL_TEST_2]);
    });

    it('with notany against a store with mixed characters and deadkeys:  NOTANY_NUL_TEST_2', function() {
      runEngineRuleSet([NOTANY_NUL_TEST_3]);
    });
  });
});