/*
 * 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.junit.client.GWTTestCase;

/**
 * Test direct uses of longs. Mostly this tests that LongLib is in fact being
 * invoked in various cases. The behavior of LongLib itself is tested in
 * LongLibTest.
 */
public class NativeLongTest extends GWTTestCase {
  /**
   * A class that wraps an int. See {@link NativeLongTest#testImplicitCastToLong()}.
   */
  private static class IntegerWrapper {
    private final int i;

    public IntegerWrapper(int i) {
      this.i = i;
    }

    public long longValue() {
      return i; // implicit cast to long
    }
  }

  private static class RequestIdFactory {
    static RequestIdFactory instance = new RequestIdFactory();

    static RequestIdFactory getInstance() {
      return instance;
    }

    long nextId;

    long getNextId() {
      return nextId++;
    }
  }

  /*
   * These constants are done as volatile fields so that the compiler will not
   * constant fold them. The problem is that if you write assertEquals(2L,
   * 4L/2L), the compiler will emit assertEquals(2L, 2L).
   */
  private static volatile byte BYTE_FOUR = (byte) 4;
  private static volatile char CHAR_FOUR = (char) 4;
  private static volatile boolean FALSE = false;
  private static volatile int INT_FOUR = 4;
  private static volatile long LONG_100 = 100L;
  private static volatile long LONG_1234 = 0x1234123412341234L;
  private static volatile long LONG_1234_DECIMAL = 1234123412341234L;
  private static volatile long LONG_1234000012340000 = 0x1234000012340000L;
  private static volatile long LONG_5DEECE66D = 0x5DEECE66DL;
  private static volatile long LONG_B = 0xBL;
  private static volatile long LONG_DEADBEEF = 0xdeadbeefdeadbeefL;
  private static volatile long LONG_DEADBEEF12341234 = 0xdeadbeef12341234L;
  private static volatile long LONG_FFFFFFFF = 0xFFFFFFFFL;
  private static volatile long LONG_FOUR = 4L;
  private static volatile long LONG_ONE = 1L;
  private static volatile long LONG_THREE = 3L;
  private static volatile long LONG_TWO = 2L;
  private static volatile long LONG_TWO_PWR_32 = 0x100000000L;
  private static volatile long LONG_ZERO = 0L;
  private static volatile short SHORT_FOUR = (short) 4;

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

  public void testArithmetic() {
    assertEquals(-1089359682551557853L, LONG_1234 + LONG_DEADBEEF);
    assertEquals(5024439901525534073L, 2 * LONG_1234 - LONG_DEADBEEF);
    assertEquals(2476047018506819212L, LONG_1234 * LONG_DEADBEEF);
    assertEquals(-240105308887621659L, LONG_DEADBEEF / 10);
    assertEquals(-1089359682551557853L, LONG_DEADBEEF % LONG_1234);
  }

  public void testArrayInitializer() {
    long[] longs = new long[3];
    assertEquals(0L, longs[1]);
    long[][] longs2 = new long[3][3];
    assertEquals(0L, longs2[1][1]);
  }

  public void testCasts() {
    assertEquals(0x12341234, (int) LONG_1234);
    assertEquals(0x1234, (short) LONG_1234);
  }

  public void testConstants() {
    assertEquals(LONG_5DEECE66D, LONG_5DEECE66D);
    assertTrue(LONG_5DEECE66D > 0L);
    assertTrue(0L < LONG_5DEECE66D);
    assertEquals(LONG_B, LONG_B);
    assertTrue(LONG_B > 0L);
    assertTrue(0L < LONG_B);
  }

  public void testFor64Bits() {
    long x = LONG_1234;
    long y = LONG_1234 + LONG_ONE;
    long z = y - x;
    // with longs implemented as doubles, z will be 0 instead of 1
    assertEquals(1L, z);
  }

  public void testImplicitCastFromLong() {
    double d = LONG_ONE;
    d += LONG_TWO;
    assertEquals(0.0, 3L, d);
    assertTrue(3L == d);

    float f = LONG_ONE;
    f += LONG_TWO;
    assertEquals(0.0, 3L, f);
    assertTrue(3L == f);
  }

  public void testImplicitCastToLong() {
    long l = 10;
    l += 5;
    assertEquals(15, l);
    assertTrue(15 == l);

    // Issue 3710
    IntegerWrapper wrap = new IntegerWrapper(20);
    assertEquals(400L, wrap.longValue() * wrap.longValue());
  }

  public void testInlinedIntInitializer() {
    long sum = 0; // int, not long!
    assertEquals("0", "" + (sum / INT_FOUR));
  }

  public void testLogicalAnd() {
    assertEquals(LONG_1234, LONG_1234 & -LONG_ONE);
    assertEquals(0x12341234L, LONG_1234 & LONG_FFFFFFFF);
    assertEquals(0L, LONG_ONE & LONG_ZERO);
    assertEquals(1L, LONG_ONE & LONG_THREE);
  }

  public void testLogicalOr() {
    assertEquals(-1L, LONG_1234 | -LONG_ONE);
    assertEquals(0x12341234FFFFFFFFL, LONG_1234 | LONG_FFFFFFFF);
    assertEquals(1L, LONG_ONE | LONG_ZERO);
    assertEquals(3L, LONG_ONE | LONG_THREE);
  }

  @SuppressWarnings("IdentityBinaryExpression")
  public void testLogicalXor() {
    assertTrue((255L ^ LONG_5DEECE66D) != 0);

    assertEquals(0L, LONG_1234 ^ LONG_1234);
    assertEquals(0x0000123400001234L, LONG_1234 ^ LONG_1234000012340000);
    assertEquals(1L, LONG_ONE ^ LONG_ZERO);
    assertEquals(2L, LONG_ONE ^ LONG_THREE);
  }

  public void testModifyingOps() {
    long l = 20;
    l += INT_FOUR;
    assertEquals(25, ++l);
    assertEquals(25, l++);
    assertEquals(26, l);
    l += BYTE_FOUR;
    assertEquals(30, l);
    l += CHAR_FOUR;
    assertEquals(34, l);
    l += SHORT_FOUR;
    assertEquals(38, l);
    l += INT_FOUR;
    assertEquals(42, l);
  }

  public void testShift() {
    assertEquals(LONG_5DEECE66D, LONG_5DEECE66D & ((LONG_ONE << 48) - 1));
    assertEquals(LONG_ONE << 12, (LONG_ONE << 60) >>> (48));

    assertTrue((LONG_ONE << 35) > (LONG_ONE << 30));
    assertEquals(0x10L, LONG_ONE << BYTE_FOUR);
    assertEquals(0x10L, LONG_ONE << CHAR_FOUR);
    assertEquals(0x10L, LONG_ONE << SHORT_FOUR);
    assertEquals(0x10L, LONG_ONE << INT_FOUR);
    assertEquals(0x10L, LONG_ONE << LONG_FOUR);

    assertEquals(1L, LONG_TWO_PWR_32 >> 32);
    assertEquals(1L, LONG_TWO_PWR_32 >>> 32);
  }

  public void testStringAppend() {
    long x = LONG_100;
    assertEquals("100 is a long", x + " is a long");
    assertEquals("a long: 100", "a long: " + x);
  }

  // Issue 1198
  public void testToHexString() {
    assertEquals("deadbeef12341234", Long.toHexString(LONG_DEADBEEF12341234));
  }

  public void testToString() {
    assertEquals("1234123412341234", "" + LONG_1234_DECIMAL);
  }

  /**
   * It's important when allocating a new temporary that it is marked as in use.
   */
  public void testVariableReuseInCompoundAssignmentNormalizer1() {
    if (FALSE) {
      // Prevent inlining, so that CAN allocates temporaries predictably.
      testVariableReuseInCompoundAssignmentNormalizer1();
    }

    assertEquals("0",
        Long.toHexString(RequestIdFactory.getInstance().getNextId()));
  }

  /**
   * Using differently typed temp variables can cause JArrayType to throw a
   * class cast exception.
   */
  public void testVariableReuseInCompoundAssignmentNormalizer2() {
    if (FALSE) {
      // Prevent inlining, so that CAN allocates temporaries predictably.
      testVariableReuseInCompoundAssignmentNormalizer2();
    }
    long ary[][] = new long[10][10];
    long i = 3, j = 3;
    assertEquals(0L, ary[(int) i++][(int) j++]++);
    assertEquals(4L, i);
    assertEquals(4L, j);
    assertEquals(1L, ary[3][3]);
  }

  /**
   * Using differently typed temp variables can cause LongEmulationNormalizer to
   * fail an assertion.
   */
  public void testVariableReuseInCompoundAssignmentNormalizer3() {
    if (FALSE) {
      // Prevent inlining, so that CAN allocates temporaries predictably.
      testVariableReuseInCompoundAssignmentNormalizer3();
    }

    long ary[] = new long[10];
    int i = 3;
    long j = 5;

    assertEquals(0L, ary[i++]++);
    assertEquals(4, i);
    assertEquals(1L, ary[3]);

    assertEquals(0L, ary[(int) j++]++);
    assertEquals(6L, j);
    assertEquals(1L, ary[5]);
  }
}
