var ar = [1,2,3];

scope {
  assert 3 === ar.#;
  assert 3 === ar.#;
  assert ar.# === 3;
  assert '3' === ar.#.toString();
  assert 2 === ar.indexOf(3);
  assert ar === ar.length(2) /* setter returns the array */;
  assert 2 === ar.#;
  assert 2 === ar[1];
  assert undefined === ar[2];

  ar.prop = 1;
  ar.clear();
  assert 1===ar.prop;
  assert !ar.#;
  ar.clear(true);
  assert undefined===ar.prop;
}

scope {
  assert ar.isEmpty();
  assert 3 === ar.push(1,2,3);
  assert !ar.isEmpty();
  assert 3 === ar.#;
  var x = 0;
  var eachCallback = proc(v,i){
    //print('in func x=',x,i);
    x = i;
      //print(x, argv, v, i);
  } ;
  ar.eachIndex( eachCallback );
  assert 2 === x;
  eachCallback(0,1);
  assert 1 === x;
}

scope {
  assert 3 === ar.#;
  assert 3 === ar.2;
  assert 3 === ar.pop();
  assert 2 === ar.#;
  assert 1 === ar.shift();
  assert 1 === ar.#;
  assert 2 === ar.0;
  assert 0 === ar.indexOf(2);
  ar.reverse();
  assert 2 === ar.0;
}

scope {
    // Array.indexOf() type-strict comparison flag...
    var a = [0,'1',2];
    assert a.indexOf(1,false) === 1 /* not type-strict */;
    assert a.indexOf(1,true) < 0 /* type-strict */;
    assert a.indexOf('1',false) === 1 /* not type-strict */;
    assert a.indexOf('1',true) === 1 /* type-strict */;
    assert a.indexOf(1) < 0 /* default == type-strict */;
}

scope {
  assert 1 === ar.#;
  assert 2 === ar.0;
  ar.unshift(1,-1);
  assert 3 === ar.#;
  assert 2 === ar.2;
  assert -1 === ar.1;
  assert 1 === ar[0];
  assert '1,-1,2' === ar.join(',');
  assert 3 === ar.setIndex(1,3);
  assert '1,2,3' === ar.sort().join(',');
  assert '3,2,1' === ar.sort(function(l,r){
    // reverse-order (l,h)...
    return l==r ? 0 : l < r ? 1 : -1;
    cannot happen;
    // The long way:
    l==r && return 0;
    l < r && return 1;
    return -1;
  }).join(',');
  assert '123' === ar.reverse().join('');
  assert ar === ar.unshift(-1);
  assert '-1123' === ar.join('');
}

scope {
  const a1 = [1,2,3];
  assert catch{a1.slice(0,-1)}.codeString() === 'CWAL_RC_RANGE';
  assert catch{a1.slice(-1,0)}.codeString() === 'CWAL_RC_RANGE';
  // slice() during sort is madness...
  assert catch a1.sort(proc(){a1.slice()}).codeString() === 'CWAL_RC_LOCKED';
    
  var a2 = a1.slice(0,1);
  assert 'array' === typename a2;
  assert 1 === a2.#;
  assert 1 === a2.0;

  const a3 = a2.slice(3);
  assert 0 === a3.#;
}

scope {
  const a1 = [1,2,3];
  var a2 = a1.slice(0, 1);
  //print('a2=',a2);
  assert 1=== a2.#;

  a2 = a1.slice(1,1);
  assert 1=== a2.#;
  assert 2 === a2.0;

  a2 = a1.slice(1, 6);
  assert 2=== a2.#;
  assert 3 === a2.1;
}

scope {
    const a = [-2,-1,0,1];
    assert -2 === a.shift();
    assert -1 === a.0;
    assert 0 === a.shift(2);
    assert 1 === a.#;
    assert 1 === a.0;
}

scope {
    const ar = [0,proc f(){assert f===this; return this},1];
    assert ar.1 === ar.1() /* 'this' is not the array */;
}

scope { // using arrays as prototypes...
    var x = {0: 0, // interesting: whether or not this property
             // gets set as an object prop or array index depends
             // on whether it gets set before or after this object
             // extends the array class...
             prototype:[1,2]
            };
    x.0 = -1 /* sets array index */;
    assert -1 === x.0 /* array index */;
    assert -1 === x.prototype.0 /* array index */;
    assert 0 === x.'0' /* string type bypasses array index check,
                          so this is an object property access.*/;
    x.'0' = 1 /* also object property, not array index */;
    assert -1 === x.0 /* string key '0' did not overwrite this */;
    x[] = 3;
    assert 3 === x.length();
    assert 3 === x.2;

    /*
      Minor descrepancy vis-a-vis objects: objects never (via
      assignment) add new properties to their prototypes, but instead
      shadow any prototype prop with the same name. Arrays index
      access does not behave that way: it modifies the array part of
      the value directly (the prototype, in the above example).
    */
}

scope {
    // Check for propagation of non-exception errors from array.sort()
    // callbacks...
    const ex = catch [1,2].sort(proc(){
        ,/*intentional syntax error*/
        assert cannot happen;
    });
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
}

scope {
    // Ensure that attempts to iterate over an array from a sort()
    // callback, or sort during iteration, are rejected...
    const ar = [1,2,3];
    var ex = catch foreach(@ar=>v) ar.sort();
    assert 'CWAL_RC_IS_VISITING_LIST' === ex.codeString();
    ex = catch ar.sort(proc(){foreach(@ar=>v){1}});
    assert 'CWAL_RC_LOCKED' === ex.codeString();

    // But multiple iteration must (as of 20191211) work...
    var i = 0;
    foreach(@ar=>v) foreach(@ar=>v) ++i;
    assert i === ar.# * ar.#;

    // Assigning during traversal is OK:
    foreach(@ar=>i,v) ar[i] = 2*v;
    assert 6 === ar.2;

    // reverse() during traversal is allowed only because it's
    // just a special case of assignment:
    foreach(@ar=>v) ar.reverse();
    assert 6 === ar.0;
    assert 2 === ar.2;
    /**
       One could rightfullyargue that sorting is also just a special
       case of get/set, but its implementation is much more involved,
       so traversing during sort, or vice versa, are currently
       disallowed.  That Way Lies Madness.
    */
}

scope {
    // Ensure that sort() never calls its callback for an empty- or
    // length-1 array...
    [].sort(proc(){cannot happen});
    [1].sort(proc(){cannot happen});
}

scope { // removeIndex()
    var x = [1,2,3,4];
    assert true === x.removeIndex(1);
    assert 3 === x.#;
    assert 1 === x[0];
    assert 3 === x[1];
    assert 4 === x[2];
    assert undefined === x[3];
}

scope { // filter()
    const isOdd = proc(v){ return v % 2 };
    var a = [1,2,3];
    var b = a.filter(isOdd);
    var c = a.filter(isOdd, true);
    assert 2 === b.#;
    assert 1===b.0 && 3===b.1;
    assert 1 === c.#;
    assert 2 === c.0;
    unset a, b, c;

    /**
       20181109: Array.filter() now accepts a Tuple 'this'. As of
       20190811, it returns a Tuple if called that way (it previously
       returned an array).
    */
    const t = [#1,2,3,4];
    assert typeinfo(istuple t);
    var ta = t.filter(isOdd);
    assert typeinfo(istuple ta);
    assert 2 === ta.#;
    assert 1 === ta.0;
    assert 3 === ta.1;
    assert [#1,3] === ta;

    ta = t.filter(isOdd,true);
    assert typeinfo(istuple ta);
    assert [#2,4] === ta;
    
    assert [#] === [#].filter(isOdd);

}

scope { // operator+=
    var a = [] /* can't be const because of += assignment :/ */;
    assert a === (a+=3);
    assert 1 === a.#;
    assert 3 === a.0;
    assert a === (a += 'b');
    assert 2 === a.#;
    assert 'b' === a.1;
    const o = {a:[]};
    assert o.a === (o.a+=1);
    assert (o.a += 2) === o.a;
    assert 2===o.a.1;
}

scope {
    /* 20191211 changes to how iteration is reported... */
    var a = [1,2,3];
    a.x = -1;
    assert !typeinfo(isiteratingprops a);
    assert !typeinfo(isiterating a);
    foreach(a=>k){
        assert typeinfo(mayiteratelist a);
        assert !typeinfo(isiteratinglist a);
        assert typeinfo(isiteratingprops a);
        assert typeinfo(isiterating a);
        foreach(@a=>v){
            assert typeinfo(isiteratinglist a);
            assert typeinfo(isiteratingprops a);
            assert typeinfo(mayiteratelist a);
            assert typeinfo(isiterating a);
        }
        assert !typeinfo(isiteratinglist a);
        assert typeinfo(isiterating a);
        assert typeinfo(isiteratingprops a);
    }
    assert !typeinfo(isiteratingprops a);
    assert !typeinfo(isiterating a);

    a.sort(proc(l,r){
        assert !typeinfo(mayiteratelist a);
        return -l.compare(r);
    });
    assert 3 === a.0;
}

scope {
    /*
      Array comparison func must internally use floating point
      comparisons.

      Changed 20191220, prompted by a bug repor against the muJS JS
      engine: https://github.com/ccxvii/mujs/issues/122
    */
    const l = [{a: 0.5}, {a: -0.1}, {a: 0.7}];
    l.sort(proc(x,y) {return x.a - y.a});
    assert 0.7===l.2.a;
    assert 0.5===l.1.a;
    assert -0.1===l.0.a;
}
