/* Demonstrate operator overloading... */  

if(1){
  assert "3.00.1" === "3.0"+0.1 /* bug chasing */;

  var pCount = 0, mCount = 0, o = {
    a: 0,
    // Binary X+Y
    'operator+': proc(l,r){
      assert 2 === argv.length();
      ++pCount;
      return this.a + r;
    },
    // X+=Y
    'operator+=': proc(l,r){
      //print('operator+=',argv);
      assert 2 === argv.length();
      ++pCount;
      this.a += r;
      return this;
    },
    // X-Y
    'operator-': proc(l,r){
      //print('operator-',argv);
      assert 2 === argv.length();
      ++mCount;
      return this.a - r;
    },
    // X-=Y
    'operator-=': proc(l,r){
      //print('operator+=',argv);
      assert 2 === argv.length();
      ++mCount;
      this.a -= r;
      return this;
    },
    // ++X if no args, X++ if 1 arg (===this).
    'operator++': proc(){
      const argc = argv.length();
      //print('operator++',argc ? 'postfix' : 'prefix', argv);
      ++pCount;
      argc ? this.a++ : ++this.a;
      return this;
    },
    // --X if no args, X-- if 1 arg (===this).
    'operator--': proc(){
      const argc = argv.length();
      //print('operator--',argc ? 'postfix' : 'prefix', argv);
      ++mCount;
      //argc ? this.a-- : --this.a;
      --this.a;
      return this;
    },
    // -X
    '-operator': proc(){
      //print('-operator', argv);
      return -this.a;
    },
    // X==Y
    'operator==': proc(l,r){
      assert l === this;
      // very minimal impl which does not detect
      // an RHS of the same class as this.
      return ((typename this) === (typename r))
             ? this.a === r.a
             : this.a == r;
    },
    // X!=Y
    'operator!=': proc(l,r){
      return !(l==r);
    },
    // X<Y
    'operator<': proc(l,r){
      //assert l === this;
      return ((typename this) === (typename r))
             ? this.a < r.a
             : this.a < r;
    },
    // X>Y
    'operator>': proc(l,r){
      //assert l === this;
      return ((typename this) === (typename r))
             ? this.a > r.a
             : this.a > r;
    }
  };

  //print("Properties:", o.propertyKeys());
  assert(!o.prototype.'operator+');
  assert o === ++o;
  assert 1 === o.a;
  assert 1 === pCount;
  assert o++ === o;
  assert 2 === o.a;
  assert 2 === pCount;
  assert o === o--;
  assert 1 === o.a;
  assert 1 === mCount;
  assert o === --o;
  // bug: can't yet distinguish -unary from --prefix
  assert 0 === o.a;
  assert 2 === mCount;
  //assert 2 === (o += 2);

  //print('o =',o);
  assert o === (o += 2);
  //print('o =',o);
  assert 4 === o + 2;
  assert 2 === o.a;
  assert 1 === o - 1;
  //assert 'CWAL_RC_NOT_FOUND' === catch{-o/*no such operator*/}.codeString();
  assert 'CWAL_RC_NOT_FOUND' === catch{+o/*no such operator*/}.codeString();
  //print("Properties:", o.propertyKeys());  
  assert 2 === o.a;
  o -= 1;
  assert 1 === o.a;
  assert -1 === -o;
  assert 1 === o.a;

  assert !(o < 1);
  assert o == 1;
  assert o != 2;
  assert o < 2;
  assert o > 0;

  assert catch{o * 1 /* no such op */};
  assert catch{o / 1 /* no such op */};
  assert catch{o % 1 /* no such op */};
  assert catch{o << 1 /* no such op */};
  assert catch{o >> 1 /* no such op */};

}

scope {
  const sproto = "".prototype;
  assert catch{"" * 3 /* no such op (yet) */};
  sproto.'operator*' = proc f(s,rhs){
    (rhs>0) || throw "Expecting positive RHS for STRING*N op.";
    f.buf || (f.buf = buffer(100));
    f.buf.reset();
    for(var i = 0; i < rhs; ++i ){
      f.buf.append(s);
    }
    return f.buf.toString();
  }.importSymbols({buffer: s2.Buffer.new});
  assert '***' === "*" * 3;
  assert catch{'*' * -1 /* negative index */};
  assert "3.00.1" === "3.0"+0.1 /* checking for a misplaced bug */;
  unset sproto.'operator*';
}

var three = 3;
scope {
  var ar = [];
  ar.'operator+=' = proc(l,r){
    l.push(r);
    return this;
  };
  ar.'operator<<' = proc(l,r){
      return l.push(r);
  };
  ar += 1;
  ar << 2;
  assert '12' === ar.join('');

  var out;
  ar.'operator>>' = proc(l,r){
    //var code = r+" = l.pop()";
    //print('code =',code);
    //return eval -> code;
    return eval -> r+"=l.pop()";
  };
  1 ? (ar >> nameof out) // yet another (obscure) use for nameof
    : ar.pop();
  assert 2 === out;
  assert 1 === ar.length();
  assert 1 === ar.0;
  //print(out,ar);
  //print(__FLC, refcount three);
  ar[3] = three;
  //print(__FLC, refcount three);
  ;1;1;1;
  //print(__FLC, refcount three);
  ar.clear();
  //print(__FLC, refcount three);

  assert catch{ar & 1/* no such operator (yet) */};
  ar.'operator&' = proc(l,r){
    return this;
  };
  assert ar === ar & 1;

}
//print(__FLC, refcount three);
assert 3 === three;

scope {
  // Overloading math ops on functions...
  var x, setX = proc(){return x = argv.0};
  setX.'operator+' = proc(self,arg){return this(arg)};
  setX+1;
  assert 1 === x;
  assert 0 === setX + 0;
  assert 1 === setX + -1 + 1 * 2;
}

scope {
  // Overload-only -> op...
  var x, setX = proc(){return x = argv.0};
  setX.'operator->' = proc(self,arg){return this(arg)};
  setX->1;
  assert 1 === x;
  assert 0 === setX -> 0;
  assert -1 === setX ->( -1 * 1 ) /* -> has . precedence */;

  unset setX;

  // Overload-only =~ ("contains") and !~ ("does not contain") ops...
  var o = {
    a: [1,2,3],
    'operator=~':proc(self,arg){
      return this.a.indexOf(arg)>=0;
    }
  };
  assert o =~ 1;
  assert o =~ 3;
  assert !(o =~ -1);

  assert catch{o !~ 1 /* no such operator yet */};
  o.'operator!~' =proc(self,arg){
    return this.a.indexOf(arg)<0;
  };

  assert o !~ -1;
  assert !(o !~ 1);
  assert o =~ 1 && o !~ -1; // has comparison precedence
}

scope {
  // C++-style streams...
  var b = s2.Buffer.new(20);
  // Remember that buffers are not containers:
  b.prototype.'operator<<' = proc(self,arg){
    return this.append(arg);
  };
  b << "a" << "bc" << "def";
  assert "abcdef" === b.toString();
  b.reset();
  b << 1 << 2+3; 
  assert "15" === b.toString();
  unset b.prototype.'operator<<';

  // Alternate implementation:
  var o = {
    buf: b.reset(),
    'operator<<': proc(self,arg){
       this.buf.append(arg);
       return this;
    }
  };
  o << "a" << "bc" << "def";
  assert "abcdef" === b.toString();
  b.reset();
  o << 1 << 2+3; 
  assert "15" === b.toString();
  unset b.prototype.'operator<<';
}


0 && scope {
// experimental, doesn't yet work how i would like
// Certain parts of this require enabling/disabling
// specific ifdefs in s2_eval.c and/or s2_ops.c

  var o = {
    __typename: 'Bob',
    'operator->': proc(self,arg){
      return this.sub[arg];
    },
    sub:{
      __typename: 'Sub',
      x: -1,
      f: proc(){
        print(__FLC,'this =',this);
        assert 'Bob' === typename this;
        return 1;
      }
    }
  };
  print(o->'f');
  assert 'function' === typename o->'f';
  assert 1 === o->f(); // works, yet...
  assert 1 === o->'f'(); // works
  o->x = 1; // assigns in o (or errors, depending on how we assign engine->dotOpLhs)
  o->x++; // as well.
  // i understand why, but a fix seems rather intrusive. i'd rather have it not behave
  // like the dot, i think.
  print(__FLC,'o =',o);
};
