/*
 * Copyright 2007 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.core.client.JavaScriptException;
import com.google.gwt.junit.client.GWTTestCase;

/**
 * Tests enum functionality.
 */
public class EnumsTest extends GWTTestCase {

  enum BarelyReferenced {
    A, B, C
  }

  enum BarelyReferenced2 {
    A, B, C
  }

  enum Basic {
    A, B, C
  }

  enum Complex {
    A("X"), B("Y"), C("Z");

    String value;

    Complex(String value) {
      this.value = value;
    }

    public String value() {
      return value;
    }
  }

  enum Subclassing {
    A {
      @Override
      public String value() {
        return "X";
      }
    },
    B {
      @Override
      public String value() {
        return "Y";
      }
    },
    C {
      @Override
      public String value() {
        return "Z";
      }
    };

    public abstract String value();
  }
  
  enum BasicWithOverloadedValueOf {
    A(1), B(2), C(3);
    
    private final int id;
    
    private BasicWithOverloadedValueOf(int id) {
      this.id = id;
    }
    
    public static BasicWithOverloadedValueOf valueOf(Integer id) {
      for (BasicWithOverloadedValueOf val : BasicWithOverloadedValueOf.values()) {
        if (val.id == id) {
          return val;
        }
      }
      throw new IllegalArgumentException();
    }
  }

  /*
   * An enum that will have a static impl generated, which will allow it to
   * become ordinalizable. Once ordinalized, need to make sure it's clinit
   * is handled properly.
   */
  enum EnumWithStaticImpl {
    VALUE1;

    // a mock formatter, to simulate the case that inspired this test
    private static class MockFormat {
      private String pattern;
      
      public MockFormat(String pattern) {
        this.pattern = pattern;
      }
    
      public String format(Long date) {
        return pattern + date.toString();
      }
    }

    private static final MockFormat FORMATTER = new MockFormat("asdf");

    public String formatDate(long date) {
      switch (this) {
        case VALUE1:
          return FORMATTER.format(new Long(date)); 
        default:
          return null;
      }
    }
  }

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

  public void testCompareTo() {
    assertTrue(Basic.A.compareTo(Basic.valueOf("A")) == 0);
    assertTrue(Basic.B.compareTo(Basic.A) > 0);
    assertTrue(Basic.A.compareTo(Basic.B) < 0);

    assertTrue(Complex.A.compareTo(Complex.valueOf("A")) == 0);
    assertTrue(Complex.B.compareTo(Complex.A) > 0);
    assertTrue(Complex.A.compareTo(Complex.B) < 0);

    assertTrue(Subclassing.A.compareTo(Subclassing.valueOf("A")) == 0);
    assertTrue(Subclassing.B.compareTo(Subclassing.A) > 0);
    assertTrue(Subclassing.A.compareTo(Subclassing.B) < 0);
  }

  public void testField() {
    assertEquals("X", Complex.A.value);
    assertEquals("Y", Complex.B.value);
    assertEquals("Z", Complex.C.value);
  }

  public void testGetDeclaringClass() {
    assertEquals(Basic.class, Basic.A.getDeclaringClass());
    assertEquals(Complex.class, Complex.A.getDeclaringClass());
    assertEquals(Subclassing.class, Subclassing.A.getDeclaringClass());
  }

  public void testMethod() {
    assertEquals("X", Complex.A.value());
    assertEquals("Y", Complex.B.value());
    assertEquals("Z", Complex.C.value());

    assertEquals("X", Subclassing.A.value());
    assertEquals("Y", Subclassing.B.value());
    assertEquals("Z", Subclassing.C.value());
  }

  public void testName() {
    assertEquals("A", Basic.A.name());
    assertEquals("B", Basic.B.name());
    assertEquals("C", Basic.C.name());

    assertEquals("A", Complex.A.name());
    assertEquals("B", Complex.B.name());
    assertEquals("C", Complex.C.name());

    assertEquals("A", Subclassing.A.name());
    assertEquals("B", Subclassing.B.name());
    assertEquals("C", Subclassing.C.name());
  }

  public void testOrdinals() {
    assertEquals(0, Basic.A.ordinal());
    assertEquals(1, Basic.B.ordinal());
    assertEquals(2, Basic.C.ordinal());

    assertEquals(0, Complex.A.ordinal());
    assertEquals(1, Complex.B.ordinal());
    assertEquals(2, Complex.C.ordinal());

    assertEquals(0, Subclassing.A.ordinal());
    assertEquals(1, Subclassing.B.ordinal());
    assertEquals(2, Subclassing.C.ordinal());
  }

  public void testSwitch() {
    switch (Basic.A) {
      case A:
        break;
      case B:
        fail("Switch failed");
        break;
      case C:
        fail("Switch failed");
        break;
      default:
        fail("Switch failed");
        break;
    }
    switch (Complex.B) {
      case A:
        fail("Switch failed");
        break;
      case B:
        break;
      case C:
        fail("Switch failed");
        break;
      default:
        fail("Switch failed");
        break;
    }
    switch (Subclassing.C) {
      case A:
        fail("Switch failed");
        break;
      case B:
        fail("Switch failed");
        break;
      default:
        break;
    }
  }

  public void testBarelyReferencedValueOf() {
    try {
      Enum.valueOf(BarelyReferenced.class, "foo");
      fail("Passed an invalid enum constant name to Enum.valueOf; expected "
          + "IllegalArgumentException");
    } catch (IllegalArgumentException e) {
      // expected
    }
  }

  public void testBarelyReferencedGetEnumConstants() {
    assertNotNull(BarelyReferenced2.class.getEnumConstants());
  }

  @SuppressWarnings("unchecked")
  public void testValueOf() {
    assertEquals(Basic.A, Basic.valueOf("A"));
    assertEquals(Basic.B, Basic.valueOf("B"));
    assertEquals(Basic.C, Basic.valueOf("C"));
    try {
      Basic.valueOf("D");
      fail("Basic.valueOf(\"D\") -- expected IllegalArgumentException");
    } catch (IllegalArgumentException e) {
    }

    assertEquals(Complex.A, Complex.valueOf("A"));
    assertEquals(Complex.B, Complex.valueOf("B"));
    assertEquals(Complex.C, Complex.valueOf("C"));
    try {
      Complex.valueOf("D");
      fail("Complex.valueOf(\"D\") -- expected IllegalArgumentException");
    } catch (IllegalArgumentException e) {
    }

    assertEquals(Subclassing.A, Subclassing.valueOf("A"));
    assertEquals(Subclassing.B, Subclassing.valueOf("B"));
    assertEquals(Subclassing.C, Subclassing.valueOf("C"));
    try {
      Subclassing.valueOf("D");
      fail("Subclassing.valueOf(\"D\") -- expected IllegalArgumentException");
    } catch (IllegalArgumentException e) {
    }
    
    enumValuesTest(Basic.class);
    enumValuesTest(Complex.class);
    enumValuesTest(Subclassing.class);
    
    try {
      Enum.valueOf(Basic.class, "foo");
      fail("Passed an invalid enum constant name to Enum.valueOf; expected "
          + "IllegalArgumentException");
    } catch (IllegalArgumentException e) {
    }
    
    try {
      @SuppressWarnings("all")
      Class fakeEnumClass = String.class;
      Enum.valueOf(fakeEnumClass, "foo");
      fail("Passed a non enum class to Enum.valueOf; expected " 
              + "IllegalArgumentException");
    } catch (IllegalArgumentException e) {       
    }
     
    try {
      Class<Basic> nullEnumClass = null;
      Enum.valueOf(nullEnumClass, "foo");
      fail("Passed a null enum class to Enum.valueOf; expected "
          + "NullPointerException");
    } catch (JavaScriptException e) {
    } catch (NullPointerException e) {
    }
      
    try {
      Enum.valueOf(Basic.class, null);
      fail("Passed a null enum constant to Enum.valueOf; expected "
          + "NullPointerException");
    } catch (NullPointerException e) {
    }
  }
  
  public void testValueOfOverload() {
    BasicWithOverloadedValueOf val1 = Enum.valueOf(BasicWithOverloadedValueOf.class,"A");
    BasicWithOverloadedValueOf val2 = BasicWithOverloadedValueOf.valueOf("B");
    BasicWithOverloadedValueOf valById1 = BasicWithOverloadedValueOf.valueOf(1);
    BasicWithOverloadedValueOf valById2 = BasicWithOverloadedValueOf.valueOf(2);
    assertEquals(val1, valById1);
    assertEquals(val2, valById2);
  }

  public void testValues() {
    Basic[] simples = Basic.values();
    assertEquals(3, simples.length);
    assertEquals(Basic.A, simples[0]);
    assertEquals(Basic.B, simples[1]);
    assertEquals(Basic.C, simples[2]);

    Complex[] complexes = Complex.values();
    assertEquals(3, complexes.length);
    assertEquals(Complex.A, complexes[0]);
    assertEquals(Complex.B, complexes[1]);
    assertEquals(Complex.C, complexes[2]);

    Subclassing[] subs = Subclassing.values();
    assertEquals(3, subs.length);
    assertEquals(Subclassing.A, subs[0]);
    assertEquals(Subclassing.B, subs[1]);
    assertEquals(Subclassing.C, subs[2]);
  }

  public void testValues_Unmodifiable() {
    Basic[] simples = Basic.values();
    simples[0] = simples[1];
    assertEquals(Basic.A, Basic.values()[0]);
  }

  /*
   * Test that a call to an enum instance method, which gets transformed to a
   * static impl, produces valid executable javascript, once the enum gets
   * ordinalized. This test is in response to a case where invalid javascript
   * was being generated, by not generating a clinit for the enum prior to
   * referencing a static impl method.
   */
  public void testEnumWithStaticImpl() {
    EnumWithStaticImpl ewsi = EnumWithStaticImpl.VALUE1;
    Long submitTime = new Long(1234567890);
    String fmtDate = ewsi.formatDate(submitTime);
    // we just need to make sure we get to this point without timing out
    assertTrue(fmtDate != null);
  }

  private <T extends Enum<T>> void enumValuesTest(Class<T> enumClass) {
    T[] constants = enumClass.getEnumConstants();
    for (T constant : constants) {
      assertEquals(constant, Enum.valueOf(enumClass, constant.name()));
    }
  }
}
