// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// Tests function statements and expressions.

class Bug4089219 {
  int x;
  var f;

  Bug4089219(int i) : this.x = i {
    f = () => x;
  }
}

class Bug4342163 {
  final m;
  Bug4342163(int a) : this.m = (() => a) {}
}

class StaticFunctionDef {
  static final int one = 1;
  static var fn1;
  static var fn2;
  static var fn3;

  static init() {
    fn1 = () { return one; };
    fn2 = () { return (() { return one; })(); };
    fn3 = () {
               final local = 1;
               return (() { return local; })();
             };
  }
}

class A {
  var ma;
  A(a) {ma = a;}
}

class B1 extends A {
  final mfn;
  B1(int a) : super(a), this.mfn = (() {return a;}) {
  }
}

class B2 extends A {
  final mfn;
  B2(int a) : super(2), this.mfn = (() {return a;}) {
  }
}

class B3 extends A {
  final mfn;
  B3(int a) : super(() {return a;}), this.mfn = (() {return a;}) {
  }
}

typedef void Fisk();

class FunctionTest {

  FunctionTest() {}

  static void testMain() {
    var test = new FunctionTest();
    test.testRecursiveClosureRef();
    test.testForEach();
    test.testVarOrder1();
    test.testVarOrder2();
    test.testLexicalClosureRef1();
    test.testLexicalClosureRef2();
    test.testLexicalClosureRef3();
    test.testLexicalClosureRef4();
    test.testLexicalClosureRef5();
    test.testFunctionScopes();
    test.testDefaultParametersOrder();
    test.testParametersOrder();
    test.testFunctionDefaults1();
    test.testFunctionDefaults2();
    test.testEscapingFunctions();
    test.testThisBinding();
    test.testFnBindingInStatics();
    test.testFnBindingInInitLists();
    test.testSubclassConstructorScopeAlias();
  }

  void testSubclassConstructorScopeAlias() {
    var b1 = new B1(10);
    Expect.equals(10, (b1.mfn)());
    Expect.equals(10, b1.ma);

    var b2 = new B2(11);
    Expect.equals(11, (b2.mfn)());
    Expect.equals(2, b2.ma);

    var b3 = new B3(12);
    Expect.equals(12, (b3.mfn)());
    Expect.equals(12, (b3.ma)());
  }

  void testFnBindingInInitLists() {
    Expect.equals(1, (new Bug4342163(1).m)());
  }

  void testFnBindingInStatics() {
    StaticFunctionDef.init();
    Expect.equals(1, ((StaticFunctionDef.fn1)()));
    Expect.equals(1, ((StaticFunctionDef.fn2)()));
    Expect.equals(1, ((StaticFunctionDef.fn3)()));
  }

  Fisk testReturnVoidFunction() {
    void f() {}
    Fisk x = f;
    return f;
  }

  void testVarOrder1() {
    var a = 0, b = a++, c = a++;

    Expect.equals(a, 2);
    Expect.equals(b, 0);
    Expect.equals(c, 1);
  }

  void testVarOrder2() {
    var a = 0;
    f() {return a++;};
    var b = f(), c = f();

    Expect.equals(a, 2);
    Expect.equals(b, 0);
    Expect.equals(c, 1);
  }

  void testLexicalClosureRef1() {
    var a = 1;
    var f, g;
    {
      var b = 2;
      f = () {return b - a;};
    }

    {
      var b = 3;
      g = () {return b - a;};
    }
    Expect.equals(1, f());
    Expect.equals(2, g());
  }

  void testLexicalClosureRef2() {
    var a = 1;
    var f, g;
    {
      var b = 2;
      f = () {return ((){return b - a;})();};
    }

    {
      var b = 3;
      g = () {return ((){return b - a;})();};
    }
    Expect.equals(1, f());
    Expect.equals(2, g());
  }

  void testLexicalClosureRef3() {
    var a = new List();
    for (int i = 0; i < 10; i++) {
      var x = i;
      a.add(() {return x;});
    }

    var sum = 0;
    for (int i = 0; i < a.length; i++) {
      sum += (a[i])();
    }

    Expect.equals(45, sum);
  }

  void testLexicalClosureRef5() {
    {
      var a;
      Expect.equals(null, a);
      a = 1;
      Expect.equals(1, a);
    }

    {
      var a;
      Expect.equals(null, a);
      a = 1;
      Expect.equals(1, a);
    }
  }

  // Make sure labels are preserved, and a second 'i' does influence the first.
  void testLexicalClosureRef4() {
    var a = new List();
    x:for (int i = 0; i < 10; i++) {
      a.add(() {return i;});
      continue x;
    }

    var sum = 0;
    for (int i = 0; i < a.length; i++) {
      sum += (a[i])();
    }

    Expect.equals(100, sum);
  }

  int tempField;

  // Validate that a closure that calls the private name of a function (for
  // for recursion) calls the version of function with the bound names.
  void testRecursiveClosureRef() {
    tempField = 2;
    var x = 3;
    var g = f(a) {
       tempField++;
       x++;
       if (a > 0) {
         f(--a);
       }
    };
    g(2);


    Expect.equals(5, tempField);
    Expect.equals(6, x);
  }

  void testForEach() {
    List<int> vals = [1,2,3];
    int total = 0;
    vals.forEach((int v) {
      total += v;
    });
    Expect.equals(6, total);
  }

  void testFunctionScopes() {
    // Function expression. 'recurse' is only defined within the function body.
    // FAILS:
    // var factorial0 = function recurse(int x) {
    //  return (x == 1) ? 1 : (x * recurse(x - 1));
    // };
    // TEMP:
    var factorial0;
    factorial0 = recurse(int x) {
      return (x == 1) ? 1 : (x * factorial0(x - 1));
    };
    // END TEMP


    // Function statement. 'factorial1' is defined in the outer scope.
    int factorial1(int x) {
      return (x == 1) ? 1 : (x * factorial1(x - 1));
    }

    // This would fail to compile if 'recurse' were defined in the outer scope.
    // Which it shouldn't be.
    int recurse = 42;

    Expect.equals(6, factorial0(3));
    Expect.equals(24, factorial0(4));
  }

  void testDefaultParametersOrder() {
    f([a = 1, b = 3]) {
      return a - b;
    }
    Expect.equals(-2, f());
  }

  void testParametersOrder() {
    f(a, b) {
      return a - b;
    }
    Expect.equals(-2, f(1,3));
  }

  void testFunctionDefaults1() {
    // TODO(jimhug): This return null shouldn't be necessary.
    f() { return null; };
    (([a = 10]) { Expect.equals(10, a); })();
    ((a, [b = 10]) { Expect.equals(10, b); })(1);
    (([a = 10]) { Expect.equals(null, a); })( f() );
    // FAILS: (([a = 10]) { Expect.equals(null ,a); })( f() );
  }

  void testFunctionDefaults2() {
    Expect.equals(10, helperFunctionDefaults2());
    Expect.equals(1, helperFunctionDefaults2(1));
  }

  num helperFunctionDefaults2([a = 10]) {
    return ((){return a;})();
  }

  void testEscapingFunctions() {
    f() { return 42; }
    (() { Expect.equals(42, f()); })();
    var o = new Bug4089219(42);
    Expect.equals(42, (o.f)());
  }

  void testThisBinding() {
    Expect.equals(this, () { return this; }());
  }
}

typedef void Foo<A, B>(A a, B b);

class Bar<A, B> {
  Foo<A, B> field;
  Bar(A a, B b) : this.field = ((A a1, B b2){}) {
    field(a, b);
  }
}

typedef UntypedFunction(arg);
typedef UntypedFunction2(arg);

class UseFunctionTypes {
  void test() {
    Function f = null;
    UntypedFunction uf = null;
    UntypedFunction2 uf2 = null;
    Foo foo = null;
    Foo<int, String> fooIntString = null;

    f = uf;
    f = uf2;
    f = foo;
    f = fooIntString;

    uf = f;
    uf2 = f;
    foo = f;
    fooIntString = f;

    foo = fooIntString;
    fooIntString = foo;

    uf = uf2;
    uf2 = uf;
  }
}

main() {
  FunctionTest.testMain();
}
