/*
 * Copyright 2013 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.dev.jjs.test;

import com.google.gwt.junit.client.GWTTestCase;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Tests Java 7 features.
 */
public class Java7Test extends GWTTestCase {

  @Override
  public String getModuleName() {
    return "com.google.gwt.dev.jjs.CompilerSuite";
  }

  // new style class literals
  int million = 1_000_000;

  int five = 0b101;

  public void testNewStyleLiterals() {
    assertEquals(1000000, million);
    assertEquals(5, five);
  }

  public void testSwitchOnString() {

    String s = "AA";
    int result = -1;
    switch (s) {
      case "BB":
        result = 0;
        break;
      case "CC":
      case "AA":
        result = 1;
        break;
    }
    assertEquals(1, result);
  }

  final List<String> log = new ArrayList<String>();

  enum ThrowsWhen {
    NEVER, ONCONSTRUCTOR, ONCLOSE;
  }

  class Resource implements AutoCloseable {

    final String name;
    final ThrowsWhen throwsWhen;

    public Resource(String name) throws E1 {
      this(name, ThrowsWhen.NEVER);
    }

    public Resource(String name, ThrowsWhen throwsWhen) throws E1 {
      this.name = name;
      this.throwsWhen = throwsWhen;
      log.add("Open " + name);
      if (throwsWhen == ThrowsWhen.ONCONSTRUCTOR) {
        throwException("ExceptionOnConstructor");
      }
    }

    public void doSomething() {
      log.add("doSomething " + name);
    }

    public void throwException(String text) throws E1 {
      throw new E1(text + " in " + name);
    }

    public void close() throws Exception {
      log.add("Close " + name);
      if (throwsWhen == ThrowsWhen.ONCLOSE) {
        throwException("ExceptionOnClose");
      }
    }
  }

  private void logException(Exception e) {
    log.add(e.getMessage());
    for (Throwable t : e.getSuppressed()) {
      log.add("Suppressed: " + t.getMessage());
    }
  }

  public void testTryWithResources_noExceptions() throws Exception {
    log.clear();
    try (
        Resource rA = new Resource("A");
        Resource rB = new Resource("B");
        Resource rC = new Resource("C")) {

      rA.doSomething();
      rB.doSomething();
      rC.doSomething();
    }

    assertContentsInOrder(log,
        "Open A",
        "Open B",
        "Open C",
        "doSomething A",
        "doSomething B",
        "doSomething C",
        "Close C",
        "Close B",
        "Close A"
    );
  }

  public void testTryWithResources_exceptions() throws Exception {
    log.clear();
    try (
        Resource rA = new Resource("A");
        Resource rB = new Resource("B", ThrowsWhen.ONCLOSE);
        Resource rC = new Resource("C")) {

      rA.doSomething();
      rB.doSomething();
      rC.doSomething();
    } catch (Exception e) {
      log.add(e.getMessage());
    } finally {
      log.add("finally");
    }

    assertContentsInOrder(log,
        "Open A",
        "Open B",
        "Open C",
        "doSomething A",
        "doSomething B",
        "doSomething C",
        "Close C",
        "Close B",
        "Close A",
        "ExceptionOnClose in B",
        "finally"
    );
  }

  public void testTryWithResources_suppressedExceptions() throws Exception {
    log.clear();
    try (
        Resource rA = new Resource("A", ThrowsWhen.ONCLOSE);
        Resource rB = new Resource("B");
        Resource rC = new Resource("C", ThrowsWhen.ONCLOSE)) {

      rA.doSomething();
      rB.doSomething();
      rC.doSomething();
    } catch (Exception e) {
      logException(e);
    }

    assertContentsInOrder(log,
        "Open A",
        "Open B",
        "Open C",
        "doSomething A",
        "doSomething B",
        "doSomething C",
        "Close C",
        "Close B",
        "Close A",
        "ExceptionOnClose in C",
        "Suppressed: ExceptionOnClose in A"
    );

    log.clear();
    try (
        Resource rA = new Resource("A");
        Resource rB = new Resource("B", ThrowsWhen.ONCLOSE);
        Resource rC = new Resource("C")) {

      rA.doSomething();
      rB.throwException("E1 here");
      rC.doSomething();
    } catch (Exception e) {
      logException(e);
    } finally {
      log.add("finally");
    }

    assertContentsInOrder(log,
        "Open A",
        "Open B",
        "Open C",
        "doSomething A",
        "Close C",
        "Close B",
        "Close A",
        "E1 here in B",
        "Suppressed: ExceptionOnClose in B",
        "finally"
    );
  }

  public void testTryWithResources_exceptionInAcquisition() {
    log.clear();
    try (
        Resource rA = new Resource("A", ThrowsWhen.ONCLOSE);
        Resource rB = new Resource("B", ThrowsWhen.ONCONSTRUCTOR);
        Resource rC = new Resource("C", ThrowsWhen.ONCLOSE)) {

      rA.doSomething();
      rB.doSomething();
      rC.doSomething();
    } catch (Exception e) {
      logException(e);
    }

    assertContentsInOrder(log,
        "Open A",
        "Open B",
        "Close A",
        "ExceptionOnConstructor in B",
        "Suppressed: ExceptionOnClose in A"
    );
  }

  public void testAddSuppressedExceptions() {
    Throwable throwable = new Throwable("primary");
    assertNotNull(throwable.getSuppressed());
    assertEquals(0, throwable.getSuppressed().length);
    Throwable suppressed1 = new Throwable("suppressed1");
    throwable.addSuppressed(suppressed1);
    assertEquals(1, throwable.getSuppressed().length);
    assertEquals(suppressed1, throwable.getSuppressed()[0]);
    Throwable suppressed2 = new Throwable("suppressed2");
    throwable.addSuppressed(suppressed2);
    assertEquals(2, throwable.getSuppressed().length);
    assertEquals(suppressed1, throwable.getSuppressed()[0]);
    assertEquals(suppressed2, throwable.getSuppressed()[1]);
  }

  static class E1 extends Exception {
    String name;
    public E1(String name) {
      this.name = name;
    }

    public int methodE1() {
      return 0;
    }

    @Override
    public String getMessage() {
      return name;
    }
  }

  static class E2 extends E1 {
    public E2(String name) {
      super(name);
    }

    public int methodE2() {
      return 1;
    }
  }

  static class E3 extends E1 {
    public E3(String name) {
      super(name);
    }

    public int methodE3() {
      return 2;
    }
  }

  public void testMultiExceptions() {

    int choose = 0;

    try {
      if (choose == 0) {
        throw new E1("e1");
      } else if (choose == 1) {
        throw new E2("e2");
      }

      fail("Exception was not trown");
    } catch (E2 | E3 x) {
      // The compiler will assign x a common supertype/superinterface of E2 and E3.
      // Here we make sure that this clause is not entered when the supertype is thrown.
      fail("Caught E1 instead of E2|E3");
    } catch (E1 x) {
    }
  }

  private Object unoptimizableId(Object o) {
    if (Math.random() > -10) {
      return o;
    }
    return null;
  }

  public void testPrimitiveCastsFromObject() {
    Object o = unoptimizableId((byte) 2);
    assertEquals((byte) 2, (byte) o);
    o = unoptimizableId((short) 3);
    assertEquals((short) 3, (short) o);
    o = unoptimizableId(1);
    assertEquals(1, (int) o);
    o = unoptimizableId(1L);
    assertEquals(1L, (long) o);
    o = unoptimizableId(0.1f);
    assertEquals(0.1f, (float) o);
    o = unoptimizableId(0.1);
    assertEquals(0.1, (double) o);
    o = unoptimizableId(true);
    assertEquals(true, (boolean) o);
    o = unoptimizableId('a');
    assertEquals('a', (char) o);
    // Test cast from supers.
    Number n = (Number) unoptimizableId(5);
    assertEquals(5, (int) n);
    Serializable s = (Serializable) unoptimizableId(6);
    assertEquals(6, (int) s);
    Comparable<Integer> c = (Comparable<Integer>) unoptimizableId(7);
    assertEquals(7, (int) c);

    // Failing casts.
    try {
      Object boxedChar = unoptimizableId('a');
      boolean b = (boolean) boxedChar;
      fail("Should have thrown a ClassCastException");
    } catch (ClassCastException e) {
      // Expected.
    }

    try {
      Object string = unoptimizableId("string");
      int num = (int) string;
      fail("Should have thrown a ClassCastException");
    } catch (ClassCastException e) {
      // Expected.
    }
  }

  private void assertContentsInOrder(Iterable<String> contents, String... elements) {
    assertEquals(Arrays.asList(elements).toString(), contents.toString());
  }
}
