/*
 * Check the order of splice's internal operations, because the ordering is
 * visible externally.
 */

function handlerMaker(obj, expected_exceptions) {
  var order = [];
  function note(trap, name)
  {
      order.push(trap + '-' + name);
      if (expected_exceptions[trap] === name) {
          throw ("fail");
      }
  }

  return [{
    /* this is the only trap we care about */
    delete: function(name) {
      note("del", name);
      return delete obj[name];
    },

    // Fundamental traps
    getOwnPropertyDescriptor: function(name) {
      var desc = Object.getOwnPropertyDescriptor(obj, name);
      // a trapping proxy's properties must always be configurable
      if (desc !== undefined)
        desc.configurable = true;
      return desc;
    },
    getPropertyDescriptor:  function(name) {
      var desc = Object.getPropertyDescriptor(obj, name); // not in ES5
      // a trapping proxy's properties must always be configurable
      if (desc !== undefined)
        desc.configurable = true;
      return desc;
    },
    getOwnPropertyNames: function() {
      return Object.getOwnPropertyNames(obj);
    },
    getPropertyNames: function() {
      return Object.getPropertyNames(obj);                // not in ES5
    },
    defineProperty: function(name, desc) {
      note("def", name);
      Object.defineProperty(obj, name, desc);
    },
    fix:          function() {
      if (Object.isFrozen(obj)) {
        return Object.getOwnPropertyNames(obj).map(function(name) {
          return Object.getOwnPropertyDescriptor(obj, name);
        });
      }
      // As long as obj is not frozen, the proxy won't allow itself to be fixed
      return undefined; // will cause a TypeError to be thrown
    },

    // derived traps
    has:          function(name) {
      note("has", name);
      return name in obj;
    },
    hasOwn:       function(name) { return Object.prototype.hasOwnProperty.call(obj, name); },
    get:          function(receiver, name) {
      note("get", name);
      return obj[name];
    },
    set:          function(receiver, name, val) {
      note("set", name);
      obj[name] = val;
      return true; // bad behavior when set fails in non-strict mode
    },
    enumerate:    function() {
      var result = [];
      for (name in obj)
        result.push(name);
      return result;
    },
    keys: function() { return Object.keys(obj) }
  }, order];
}

// arr: the array to splice
// expected_order: the expected order of operations on arr, stringified
function check_splice_proxy(arr, expected_order, expected_exceptions, expected_array, expected_result) {
    print (arr);
    var [handler, store] = handlerMaker(arr, expected_exceptions);
    var proxy = Proxy.create(handler);

    try {
        var args = Array.prototype.slice.call(arguments, 5);
        var result = Array.prototype.splice.apply(proxy, args);
        assertEq(Object.keys(expected_exceptions).length, 0);
    } catch (e) {
        assertEq(Object.keys(expected_exceptions).length > 0, true);
    }

    // check the order of the property accesses, etc
    assertEq(store.toString(), expected_order);

    // The deleted elements are returned in an object that's always an Array.
    assertEq(Array.isArray(result) || result === undefined, true);

    // check the return value
    for (var i in expected_result) {
        assertEq(result[i], expected_result[i]);
    }
    for (var i in result) {
        assertEq(result[i], expected_result[i]);
    }

    // check the value of arr
    for (var i in expected_array) {
        assertEq(arr[i], expected_array[i]);
    }
    for (var i in arr) {
        assertEq(arr[i], expected_array[i]);
    }

    return result;
}

// Shrinking array
check_splice_proxy(
        [10,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-3,get-3,set-0,has-4,get-4,set-1,has-5,get-5,set-2," +
        "del-5,del-4,del-3," +
        "set-length",
        {},
        [3,4,5],
        [10,1,2],
        0, 3
);

// Growing array
check_splice_proxy(
        [11,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-5,get-5,set-9,has-4,get-4,set-8,has-3,get-3,set-7," +
        "set-0,set-1,set-2,set-3,set-4,set-5,set-6," +
        "set-length",
        {},
        [9,9,9,9,9,9,9,3,4,5],
        [11,1,2],
        0, 3, 9, 9, 9, 9, 9, 9, 9
);

// Same sized array
check_splice_proxy(
        [12,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "set-0,set-1,set-2," +
        "set-length",
        {},
        [9,9,9,3,4,5],
        [12,1,2],
        0, 3, 9, 9, 9
);


/*
 * Check that if we fail at a particular step in the algorithm, we don't
 * continue with the algorithm beyond that step.
 */


// Step 3: fail when getting length
check_splice_proxy(
        [13,1,2,3,4,5],
        "get-length",
        {get: 'length'},
        [13,1,2,3,4,5],
        undefined,
        0, 3, 9, 9, 9
);

// Step 9b: fail when [[HasProperty]]
check_splice_proxy(
        [14,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1",
        {has: '1'},
        [14,1,2,3,4,5],
        undefined,
        0, 3, 9, 9, 9
);

// Step 9c(i): fail when [[Get]]
check_splice_proxy(
        [15,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1",
        {get: '1'},
        [15,1,2,3,4,5],
        undefined,
        0, 3, 9, 9, 9
);

// Step 12b(iii): fail when [[HasProperty]]
check_splice_proxy(
        [16,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-3,get-3,set-0,has-4",
        {has: '4'},
        [3,1,2,3,4,5],
        undefined,
        0, 3
);


// Step 12b(iv)1: fail when [[Get]]
check_splice_proxy(
        [17,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-3,get-3,set-0,has-4,get-4",
        {get: '4'},
        [3,1,2,3,4,5],
        undefined,
        0, 3
);


// Step 12b(iv)2: fail when [[Put]]
check_splice_proxy(
        [18,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-3,get-3,set-0,has-4,get-4,set-1",
        {set: '1'},
        [3,1,2,3,4,5],
        undefined,
        0, 3
);

// Step 12b(v)1: fail when [[Delete]]
check_splice_proxy(
        [19,1,2,3,,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-3,get-3,set-0,has-4,del-1",
        {del: '1'},
        [3,1,2,3,,5],
        undefined,
        0, 3
);

// Step 12d(i): fail when [[Delete]]
check_splice_proxy(
        [20,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-3,get-3,set-0,has-4,get-4,set-1,has-5,get-5,set-2," +
        "del-5,del-4",
        {del: '4'},
        [3,4,5,3,4],
        undefined,
        0, 3
);

// Step 13b(iii): fail when [[HasProperty]]
check_splice_proxy(
        [21,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-5,get-5,set-8,has-4",
        {has: '4'},
        [21,1,2,3,4,5,,,5],
        undefined,
        0, 3, 9,9,9,9,9,9
);


// Step 13b(iv)1: fail when [[Get]]
check_splice_proxy(
        [22,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-5,get-5,set-8,has-4,get-4",
        {get: '4'},
        [22,1,2,3,4,5,,,5],
        undefined,
        0, 3, 9,9,9,9,9,9
);


// Step 13b(iv)2: fail when [[Put]]
check_splice_proxy(
        [23,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-5,get-5,set-8,has-4,get-4,set-7",
        {set: '7'},
        [23,1,2,3,4,5,,,5],
        undefined,
        0, 3, 9,9,9,9,9,9
);

// Step 13b(v)1: fail when [[Delete]]
check_splice_proxy(
        [24,1,2,3,,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-5,get-5,set-8,has-4,del-7",
        {del: '7'},
        [24,1,2,3,,5,,,5],
        undefined,
        0, 3, 9,9,9,9,9,9
);

// Step 15b: fail when [[Put]]
check_splice_proxy(
        [25,1,2,3,4,5],
        "get-length," +
        "has-0,get-0,has-1,get-1,has-2,get-2," +
        "has-5,get-5,set-8,has-4,get-4,set-7,has-3,get-3,set-6," +
        "set-0,set-1,set-2",
        {set: '2'},
        [9,9,2,3,4,5,3,4,5],
        undefined,
        0, 3, 9,9,9,9,9,9
);
