// 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.
// Dart test program testing closures.

class LocalFunctionTest {
  LocalFunctionTest() : field1 = 100, field2_ = 200  { }
  static int f(int n) {
    int a = 0;
    g(int m) {
      a = 3*n + m + 1;  // Capture parameter n and local a.
      return a;
    }
    var b = g(n);
    return a + b;
  }
  static int h(int n) {
    k(int n) {
      var a = new List(n);
      var b = new List(n);
      for (int i = 0; i < n; i++) {
        var j = i;
        a[i] = () => i;  // Captured i is always n.
        b[i] = () => j;  // Captured j varies from 0 to n-1.
      }
      var a_sum = 0;
      var b_sum = 0;
      for (int i = 0; i < n; i++) {
        a_sum += a[i]();
        b_sum += b[i]();
      }
      return a_sum + b_sum;
    }
    return k(n);
  }
  int field1;
  int field2_;
  int get field2() { return field2_; }
  void set field2(int value) { field2_ = value; }

  int method(int n) {
    incField1() { field1++; }
    incField2() { field2++; }
    for (int i = 0; i < n; i++) {
      incField1();
      incField2();
    }
    return field1 + field2;
  }
  int execute(int times, apply(int x)) {
    for (int i = 0; i < times; i++) {
      apply(i);
    }
    return field1;
  }
  int testExecute(int n) {
    execute(n, (int x) { field1 += x; });
    return field1;
  }
  static int foo(int n) {
    return -100;  // Wrong foo.
  }
  static testSelfReference1(int n) {
    int foo(int n) {
      if (n == 0) {
        return 0;
      } else {
        return 1 + foo(n - 1);  // Local foo, not static foo.
      }
    };
    return foo(n);  // Local foo, not static foo.
  }
  static void hep(Function f) {
    f();
  }
  static testSelfReference3(int n) {
    int i = 0;
    var yup;  // Not in same scope as yup below.
    hep(yup() {
      if (++i < n) hep(yup);
    });
    return i;
  }
  static testNesting(int n) {
    var a = new List(n*n);
    f0() {
      for (int i = 0; i < n; i++) {
        int vi = i;
        f1() {
          for (int j = 0; j < n; j++) {
            int vj = j;
            a[i*n + j] = () => vi*n + vj;
          }
        }
        f1();
      }
    }
    f0();
    int result = 0;
    for (int k = 0; k < n*n; k++) {
      Expect.equals(k, a[k]());
      result += a[k]();
    }
    return result;
  }

  static var field5;
  static var set_field5_func;
  static testClosureCallStatement(int x) {
    LocalFunctionTest.set_field5_func = (int n) { field5 = n * n; };
    (LocalFunctionTest.set_field5_func)(x);
    Expect.equals(x * x, LocalFunctionTest.field5);
    return true;
  }

  static testExceptions() {
    var f = (int n) => n + 1;
    Expect.equals(2, f(1));
    Expect.equals(true, f is Function);
    Expect.equals(true, f is Object);
    Expect.equals("Closure", f.toString());
    bool exception_caught = false;
    try {
      f(1, 2);
    } catch (ClosureArgumentMismatchException e) {
      exception_caught = true;
    }
    Expect.equals(true, exception_caught);
    exception_caught = false;
    try {
      f();
    } catch (ClosureArgumentMismatchException e) {
      exception_caught = true;
    }
    Expect.equals(true, exception_caught);
    exception_caught = false;
    try {
      f.xyz(0);
    } catch (NoSuchMethodException e) {
      exception_caught = true;
    }
    Expect.equals(true, exception_caught);

    // Overwrite closure value.
    f = 3;
    exception_caught = false;
    try {
      f(1);
    } catch (ObjectNotClosureException e) {
      exception_caught = true;
    }
    Expect.equals(true, exception_caught);

    // Do not expect any exceptions to be thrown.
    var g = ([int n = 1]) => n + 1;
    Expect.equals(2, g());
    Expect.equals(3, g(2));
  }

  static int doThis(int n, int f(int n)) {
    return f(n);
  }

  static testMain() {
    Expect.equals(2*(3*2 + 2 + 1), f(2));
    Expect.equals(10*10 + 10*9/2, h(10));
    Expect.equals(320, new LocalFunctionTest().method(10));
    Expect.equals(145, new LocalFunctionTest().testExecute(10));
    Expect.equals(5, testSelfReference1(5));
    Expect.equals(5, testSelfReference3(5));
    Expect.equals(24*25/2, testNesting(5));
    Expect.equals(true, testClosureCallStatement(7));
    Expect.equals(99, doThis(10, int _(n) => n * n - 1));
    Expect.equals(99, doThis(10, int f(n) => n * n - 1));
    Expect.equals(99, doThis(10, (n) => n * n - 1));
    Expect.equals(99, doThis(10, f(n) => n * n - 1));
    testExceptions();
  }
}

main() {
  LocalFunctionTest.testMain();
}
