// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package go;

import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;

import java.util.Arrays;
import java.util.Random;

import go.testpkg.*;
import go.secondpkg.Secondpkg;

public class SeqTest extends InstrumentationTestCase {
  public SeqTest() {
  }

  public void testConst() {
    assertEquals("const String", "a string", Testpkg.AString);
    assertEquals("const Int", 7, Testpkg.AnInt);
    assertEquals("const Bool", true, Testpkg.ABool);
    assertEquals("const Float", 0.12345, Testpkg.AFloat, 0.0001);

    assertEquals("const MinInt32", -1<<31, Testpkg.MinInt32);
    assertEquals("const MaxInt32", (1<<31) - 1, Testpkg.MaxInt32);
    assertEquals("const MinInt64", -1L<<63, Testpkg.MinInt64);
    assertEquals("const MaxInt64", (1L<<63) - 1, Testpkg.MaxInt64);
    assertEquals("const SmallestNonzeroFloat64", 4.940656458412465441765687928682213723651e-324, Testpkg.SmallestNonzeroFloat64, 1e-323);
    assertEquals("const MaxFloat64", 1.797693134862315708145274237317043567981e+308, Testpkg.MaxFloat64, 0.0001);
    assertEquals("const SmallestNonzeroFloat32", 1.401298464324817070923729583289916131280e-45, Testpkg.SmallestNonzeroFloat32, 1e-44);
    assertEquals("const MaxFloat32", 3.40282346638528859811704183484516925440e+38, Testpkg.MaxFloat32, 0.0001);
    assertEquals("const Log2E", 1/0.693147180559945309417232121458176568075500134360255254120680009, Testpkg.Log2E, 0.0001);
  }

  public void testRefMap() {
    // Ensure that the RefMap.live count is kept in sync
    // even a particular reference number is removed and
    // added again
    Seq.RefMap m = new Seq.RefMap();
    Seq.Ref r = new Seq.Ref(1, null);
    m.put(r.refnum, r);
    m.remove(r.refnum);
    m.put(r.refnum, r);
    // Force the RefMap to grow, to activate the sanity
    // checking of the live count in RefMap.grow.
    for (int i = 2; i < 24; i++) {
      m.put(i, new Seq.Ref(i, null));
    }
  }

  public void testVar() {
    assertEquals("var StringVar", "a string var", Testpkg.getStringVar());

    String newStringVar = "a new string var";
    Testpkg.setStringVar(newStringVar);
    assertEquals("var StringVar", newStringVar, Testpkg.getStringVar());

    assertEquals("var IntVar", 77, Testpkg.getIntVar());

    long newIntVar = 777;
    Testpkg.setIntVar(newIntVar);
    assertEquals("var IntVar", newIntVar, Testpkg.getIntVar());

    S s0 = Testpkg.getStructVar();
    assertEquals("var StructVar", "a struct var", s0.string());
    S s1 = Testpkg.new_();
    Testpkg.setStructVar(s1);
    assertEquals("var StructVar", s1.string(), Testpkg.getStructVar().string());

    AnI obj = new AnI();
    obj.name = "this is an I";
    Testpkg.setInterfaceVar(obj);
    assertEquals("var InterfaceVar", obj.string(), Testpkg.getInterfaceVar().string());
  }

  public void testAssets() {
    // Make sure that a valid context is set before reading assets
    Seq.setContext(getInstrumentation().getContext());
    String want = "Hello, Assets.\n";
    String got = Testpkg.readAsset();
    assertEquals("Asset read", want, got);
  }

  public void testAdd() {
    long res = Testpkg.add(3, 4);
    assertEquals("Unexpected arithmetic failure", 7, res);
  }

  public void testBool() {
    assertTrue(Testpkg.negate(false));
    assertFalse(Testpkg.negate(true));
  }

  public void testShortString() {
    String want = "a short string";
    String got = Testpkg.strDup(want);
    assertEquals("Strings should match", want, got);

    want = "";
    got = Testpkg.strDup(want);
    assertEquals("Strings should match (empty string)", want, got);

    got = Testpkg.strDup(null);
    assertEquals("Strings should match (null string)", want, got);
  }

  public void testLongString() {
    StringBuilder b = new StringBuilder();
    for (int i = 0; i < 128*1024; i++) {
      b.append("0123456789");
    }
    String want = b.toString();
    String got = Testpkg.strDup(want);
    assertEquals("Strings should match", want, got);
  }

  public void testUnicode() {
    String[] tests = new String[]{
      "abcxyz09{}",
      "Hello, 世界",
      "\uffff\uD800\uDC00\uD800\uDC01\uD808\uDF45\uDBFF\uDFFF",
      // From Go std lib tests in unicode/utf16/utf16_test.go
      "\u0001\u0002\u0003\u0004",
      "\uffff\ud800\udc00\ud800\udc01\ud808\udf45\udbff\udfff",
      "\ud800a",
      "\udfff"
    };
    String[] wants = new String[]{
      "abcxyz09{}",
      "Hello, 世界",
      "\uffff\uD800\uDC00\uD800\uDC01\uD808\uDF45\uDBFF\uDFFF",
      "\u0001\u0002\u0003\u0004",
      "\uffff\ud800\udc00\ud800\udc01\ud808\udf45\udbff\udfff",
      "\ufffda",
      "\ufffd"
    };
    for (int i = 0; i < tests.length; i++) {
      String got = Testpkg.strDup(tests[i]);
      String want = wants[i];
      assertEquals("Strings should match", want, got);
    }
  }

  public void testNilErr() throws Exception {
    Testpkg.err(null); // returns nil, no exception
  }

  public void testErr() {
    String msg = "Go errors are dropped into the confusing space of exceptions";
    try {
      Testpkg.err(msg);
      fail("expected non-nil error to be turned into an exception");
    } catch (Exception e) {
      assertEquals("messages should match", msg, e.getMessage());
    }
  }

  public void testByteArray() {
    for (int i = 0; i < 2048; i++) {
      if (i == 0) {
        byte[] got = Testpkg.bytesAppend(null, null);
        assertEquals("Bytes(null+null) should match", (byte[])null, got);
        got = Testpkg.bytesAppend(new byte[0], new byte[0]);
        assertEquals("Bytes(empty+empty) should match", (byte[])null, got);
        continue;
      }

      byte[] want = new byte[i];
      new Random().nextBytes(want);

      byte[] s1 = null;
      byte[] s2 = null;
      if (i > 0) {
        s1 = Arrays.copyOfRange(want, 0, 1);
      }
      if (i > 1) {
        s2 = Arrays.copyOfRange(want, 1, i);
      }
      byte[] got = Testpkg.bytesAppend(s1, s2);
      MoreAsserts.assertEquals("Bytes(len="+i+") should match", want, got);
    }
  }

  // Test for golang.org/issue/9486.
  public void testByteArrayAfterString() {
    byte[] bytes = new byte[1024];
    for (int i=0; i < bytes.length; i++) {
           bytes[i] = 8;
    }

    String stuff = "stuff";
    byte[] got = Testpkg.appendToString(stuff, bytes);

    try {
      byte[] s = stuff.getBytes("UTF-8");
      byte[] want = new byte[s.length + bytes.length];
      System.arraycopy(s, 0, want, 0, s.length);
      System.arraycopy(bytes, 0, want, s.length, bytes.length);
      MoreAsserts.assertEquals("Bytes should match", want, got);
    } catch (Exception e) {
      fail("Cannot perform the test: " + e.toString());
    }
  }

  public void testGoRefGC() {
    S s = Testpkg.new_();
    runGC();
    long collected = Testpkg.numSCollected();
    assertEquals("Only S should be pinned", 0, collected);

    s = null;
    runGC();
    collected = Testpkg.numSCollected();
    assertEquals("S should be collected", 1, collected);
  }

  private class AnI implements I {
    public void e() throws Exception {
      throw new Exception("my exception from E");
    }

    boolean calledF;
    public void f() {
      calledF = true;
    }

    public I i() {
      return this;
    }

    public S s() {
      return Testpkg.new_();
    }

    public String stoString(S s) {
      return s.string();
    }

    public long v() {
      return 1234;
    }

    public long ve() throws Exception {
      throw new Exception("my exception from VE");
    }

    public String name;

    public String string() {
      return name;
    }

  }

  // TODO(hyangah): add tests for methods that take parameters.

  public void testInterfaceMethodReturnsError() {
    final AnI obj = new AnI();
    try {
      Testpkg.callE(obj);
      fail("Expecting exception but none was thrown.");
    } catch (Exception e) {
      assertEquals("Error messages should match", "my exception from E", e.getMessage());
    }
  }

  public void testInterfaceMethodVoid() {
    final AnI obj = new AnI();
    Testpkg.callF(obj);
    assertTrue("Want AnI.F to be called", obj.calledF);
  }

  public void testInterfaceMethodReturnsInterface() {
    AnI obj = new AnI();
    obj.name = "testing AnI.I";
    I i = Testpkg.callI(obj);
    assertEquals("Want AnI.I to return itself", i.string(), obj.string());

    runGC();

    i = Testpkg.callI(obj);
    assertEquals("Want AnI.I to return itself", i.string(), obj.string());
  }

  public void testInterfaceMethodReturnsStructPointer() {
    final AnI obj = new AnI();
    for (int i = 0; i < 5; i++) {
      S s = Testpkg.callS(obj);
      runGC();
    }
  }

  public void testInterfaceMethodTakesStructPointer() {
    final AnI obj = new AnI();
    S s = Testpkg.callS(obj);
    String got = obj.stoString(s);
    String want = s.string();
    assertEquals("Want AnI.StoString(s) to call s's String", want, got);
  }

  public void testInterfaceMethodReturnsInt() {
    final AnI obj = new AnI();
    assertEquals("Values must match", 1234, Testpkg.callV(obj));
  }

  public void testInterfaceMethodReturnsIntOrError() {
    final AnI obj = new AnI();
    try {
      long v = Testpkg.callVE(obj);
      fail("Expecting exception but none was thrown and got value " + v);
    } catch (Exception e) {
      assertEquals("Error messages should match", "my exception from VE", e.getMessage());
    }
  }

  boolean finalizedAnI;

  private class AnI_Traced extends AnI {
    @Override
    public void finalize() throws Throwable {
      finalizedAnI = true;
      super.finalize();
    }
  }

  public void testJavaRefGC() {
    finalizedAnI = false;
    AnI obj = new AnI_Traced();
    Testpkg.callF(obj);
    assertTrue("want F to be called", obj.calledF);
    Testpkg.callF(obj);
    obj = null;
    runGC();
    assertTrue("want obj to be collected", finalizedAnI);
  }

  public void testJavaRefKeep() {
    finalizedAnI = false;
    AnI obj = new AnI_Traced();
    Testpkg.callF(obj);
    Testpkg.callF(obj);
    obj = null;
    runGC();
    assertTrue("want obj not to be kept by Go", finalizedAnI);

    finalizedAnI = false;
    obj = new AnI_Traced();
    Testpkg.keep(obj);
    obj = null;
    runGC();
    assertFalse("want obj to be kept live by Go", finalizedAnI);
  }

  private int countI = 0;

  private class CountI implements I {
    public void f() { countI++; }

    public void e() throws Exception {}
    public I i() { return null; }
    public S s() { return null; }
    public String stoString(S s) { return ""; }
    public long v() { return 0; }
    public long ve() throws Exception { return 0; }
    public String string() { return ""; }
  }

  public void testGoRefMapGrow() {
    CountI obj = new CountI();
    Testpkg.keep(obj);

    // Push active references beyond base map size.
    for (int i = 0; i < 24; i++) {
      CountI o = new CountI();
      Testpkg.callF(o);
      if (i%3==0) {
        Testpkg.keep(o);
      }
    }
    runGC();
    for (int i = 0; i < 128; i++) {
      Testpkg.callF(new CountI());
    }

    Testpkg.callF(obj); // original object needs to work.

    assertEquals(countI, 1+24+128);
  }

  private void runGC() {
    System.gc();
    System.runFinalization();
    Testpkg.gc();
    System.gc();
    System.runFinalization();
  }

  public void testUnnamedParams() {
    final String msg = "1234567";
    assertEquals("want the length of \"1234567\" passed after unnamed params",
		    7, Testpkg.unnamedParams(10, 20, msg));
  }

  public void testPointerToStructAsField() {
    Node a = Testpkg.newNode("A");
    Node b = Testpkg.newNode("B");
    a.setNext(b);
    String got = a.string();
    assertEquals("want Node A points to Node B", "A:B:<end>", got);
  }

  public void testImplementsInterface() {
    Interface intf = Testpkg.newConcrete();
  }

  public void testErrorField() {
    final String want = "an error message";
    Node n = Testpkg.newNode("ErrTest");
    n.setErr(want);
    String got = n.getErr();
    assertEquals("want back the error message we set", want, got);
  }

  //test if we have JNI local reference table overflow error
  public void testLocalReferenceOverflow() {
    Testpkg.callWithCallback(new GoCallback() {

      @Override
      public void varUpdate() {
        //do nothing
      }
    });
  }

  public void testNullReferences() {
    assertTrue(Testpkg.callWithNull(null, new NullTest() {
      public NullTest null_() {
        return null;
      }
    }));
	assertEquals("Go nil interface is null", null, Testpkg.newNullInterface());
	assertEquals("Go nil struct pointer is null", null, Testpkg.newNullStruct());
  }

  public void testPassByteArray() {
    Testpkg.passByteArray(new B() {
      @Override public void b(byte[] b) {
        byte[] want = new byte[]{1, 2, 3, 4};
        MoreAsserts.assertEquals("bytes should match", want, b);
      }
    });
  }

  public void testReader() {
    byte[] b = new byte[8];
    try {
      long n = Testpkg.readIntoByteArray(b);
      assertEquals("wrote to the entire byte array", b.length, n);
      byte[] want = new byte[b.length];
      for (int i = 0; i < want.length; i++)
        want[i] = (byte)i;
      MoreAsserts.assertEquals("bytes should match", want, b);
     } catch (Exception e) {
       fail("Failed to write: " + e.toString());
     }
  }

  public void testGoroutineCallback() {
    Testpkg.goroutineCallback(new Receiver() {
      @Override public void hello(String msg) {
      }
    });
  }

  public void testImportedPkg() {
    Testpkg.callImportedI(new go.secondpkg.I() {
      @Override public long f(long i) {
        return i;
      }
    });
    assertEquals("imported string should match", Secondpkg.HelloString, Secondpkg.hello());
    go.secondpkg.I i = Testpkg.newImportedI();
    go.secondpkg.S s = Testpkg.newImportedS();
    i = Testpkg.getImportedVarI();
    s = Testpkg.getImportedVarS();
    assertEquals("numbers should match", 8, i.f(8));
    assertEquals("numbers should match", 8, s.f(8));
    Testpkg.setImportedVarI(i);
    Testpkg.setImportedVarS(s);
    ImportedFields fields = Testpkg.newImportedFields();
    i = fields.getI();
    s = fields.getS();
    fields.setI(i);
    fields.setS(s);
    Testpkg.withImportedI(i);
    Testpkg.withImportedS(s);

    go.secondpkg.IF f = new AnI();
    f = Testpkg.new_();
    go.secondpkg.Ser ser = Testpkg.newSer();
  }

  public void testRoundtripEquality() {
    I want = new AnI();
    assertTrue("java object passed through Go should not be wrapped", want == Testpkg.iDup(want));
    InterfaceDupper idup = new InterfaceDupper(){
      @Override public Interface iDup(Interface i) {
        return i;
      }
    };
    assertTrue("Go interface passed through Java should not be wrapped", Testpkg.callIDupper(idup));
    ConcreteDupper cdup = new ConcreteDupper(){
      @Override public Concrete cDup(Concrete c) {
        return c;
      }
    };
    assertTrue("Go struct passed through Java should not be wrapped", Testpkg.callCDupper(cdup));
  }

  public void testEmptyError() {
    try {
      Testpkg.emptyError();
      fail("Empty error wasn't caught");
    } catch (Exception e) {
    }
    EmptyErrorer empty = new EmptyErrorer() {
      @Override public void emptyError() throws Exception {
        throw new Exception("");
      }
    };
    try {
      Testpkg.callEmptyError(empty);
      fail("Empty exception wasn't caught");
    } catch (Exception e) {
    }
  }

  public void testInitCaller() {
    Testpkg.init();

    InitCaller initer = Testpkg.newInitCaller();
    initer.init();
  }
}
