/*
 * Written by Doug Lea and Martin Buchholz with assistance from
 * members of JCP JSR-166 Expert Group and released to the public
 * domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

/*
 * Source:
 * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/test/tck-jsr166e/AtomicDoubleTest.java?revision=1.8
 * (Modified to adapt to guava coding conventions)
 */

package com.google.common.util.concurrent;

/**
 * Unit test for {@link AtomicDouble}.
 */
public class AtomicDoubleTest extends JSR166TestCase {

  private static final double[] VALUES = {
    Double.NEGATIVE_INFINITY,
    -Double.MAX_VALUE,
    (double) Long.MIN_VALUE,
    (double) Integer.MIN_VALUE,
    -Math.PI,
    -1.0,
    -Double.MIN_VALUE,
    -0.0,
    +0.0,
    Double.MIN_VALUE,
    1.0,
    Math.PI,
    (double) Integer.MAX_VALUE,
    (double) Long.MAX_VALUE,
    Double.MAX_VALUE,
    Double.POSITIVE_INFINITY,
    Double.NaN,
    Float.MAX_VALUE,
  };

  /** The notion of equality used by AtomicDouble */
  static boolean bitEquals(double x, double y) {
    return Double.doubleToRawLongBits(x) == Double.doubleToRawLongBits(y);
  }

  static void assertBitEquals(double x, double y) {
    assertEquals(Double.doubleToRawLongBits(x),
                 Double.doubleToRawLongBits(y));
  }

  /**
   * constructor initializes to given value
   */
  public void testConstructor() {
    for (double x : VALUES) {
      AtomicDouble a = new AtomicDouble(x);
      assertBitEquals(x, a.get());
    }
  }

  /**
   * default constructed initializes to zero
   */
  public void testConstructor2() {
    AtomicDouble a = new AtomicDouble();
    assertBitEquals(0.0, a.get());
  }

  /**
   * get returns the last value set
   */
  public void testGetSet() {
    AtomicDouble at = new AtomicDouble(1.0);
    assertBitEquals(1.0, at.get());
    for (double x : VALUES) {
      at.set(x);
      assertBitEquals(x, at.get());
    }
  }

  /**
   * get returns the last value lazySet in same thread
   */
  public void testGetLazySet() {
    AtomicDouble at = new AtomicDouble(1.0);
    assertBitEquals(1.0, at.get());
    for (double x : VALUES) {
      at.lazySet(x);
      assertBitEquals(x, at.get());
    }
  }

  /**
   * compareAndSet succeeds in changing value if equal to expected else fails
   */
  public void testCompareAndSet() {
    double prev = Math.E;
    double unused = Math.E + Math.PI;
    AtomicDouble at = new AtomicDouble(prev);
    for (double x : VALUES) {
      assertBitEquals(prev, at.get());
      assertFalse(at.compareAndSet(unused, x));
      assertBitEquals(prev, at.get());
      assertTrue(at.compareAndSet(prev, x));
      assertBitEquals(x, at.get());
      prev = x;
    }
  }

  /**
   * compareAndSet in one thread enables another waiting for value
   * to succeed
   */

      public void testCompareAndSetInMultipleThreads() throws Exception {
    final AtomicDouble at = new AtomicDouble(1.0);
    Thread t = newStartedThread(new CheckedRunnable() {
        public void realRun() {
          while (!at.compareAndSet(2.0, 3.0)) {
            Thread.yield();
          }
        }});

    assertTrue(at.compareAndSet(1.0, 2.0));
    awaitTermination(t);
    assertBitEquals(3.0, at.get());
  }

  /**
   * repeated weakCompareAndSet succeeds in changing value when equal
   * to expected
   */
  public void testWeakCompareAndSet() {
    double prev = Math.E;
    double unused = Math.E + Math.PI;
    AtomicDouble at = new AtomicDouble(prev);
    for (double x : VALUES) {
      assertBitEquals(prev, at.get());
      assertFalse(at.weakCompareAndSet(unused, x));
      assertBitEquals(prev, at.get());
      while (!at.weakCompareAndSet(prev, x)) {
        ;
      }
      assertBitEquals(x, at.get());
      prev = x;
    }
  }

  /**
   * getAndSet returns previous value and sets to given value
   */
  public void testGetAndSet() {
    double prev = Math.E;
    AtomicDouble at = new AtomicDouble(prev);
    for (double x : VALUES) {
      assertBitEquals(prev, at.getAndSet(x));
      prev = x;
    }
  }

  /**
   * getAndAdd returns previous value and adds given value
   */
  public void testGetAndAdd() {
    for (double x : VALUES) {
      for (double y : VALUES) {
        AtomicDouble a = new AtomicDouble(x);
        double z = a.getAndAdd(y);
        assertBitEquals(x, z);
        assertBitEquals(x + y, a.get());
      }
    }
  }

  /**
   * addAndGet adds given value to current, and returns current value
   */
  public void testAddAndGet() {
    for (double x : VALUES) {
      for (double y : VALUES) {
        AtomicDouble a = new AtomicDouble(x);
        double z = a.addAndGet(y);
        assertBitEquals(x + y, z);
        assertBitEquals(x + y, a.get());
      }
    }
  }

  /**
   * a deserialized serialized atomic holds same value
   */
  public void testSerialization() throws Exception {
    AtomicDouble a = new AtomicDouble();
    AtomicDouble b = serialClone(a);
    assertNotSame(a, b);
    a.set(-22.0);
    AtomicDouble c = serialClone(a);
    assertNotSame(b, c);
    assertBitEquals(-22.0, a.get());
    assertBitEquals(0.0, b.get());
    assertBitEquals(-22.0, c.get());
    for (double x : VALUES) {
      AtomicDouble d = new AtomicDouble(x);
      assertBitEquals(serialClone(d).get(), d.get());
    }
  }

  /**
   * toString returns current value
   */
  public void testToString() {
    AtomicDouble at = new AtomicDouble();
    assertEquals("0.0", at.toString());
    for (double x : VALUES) {
      at.set(x);
      assertEquals(Double.toString(x), at.toString());
    }
  }

  /**
   * intValue returns current value.
   */
  public void testIntValue() {
    AtomicDouble at = new AtomicDouble();
    assertEquals(0, at.intValue());
    for (double x : VALUES) {
      at.set(x);
      assertEquals((int) x, at.intValue());
    }
  }

  /**
   * longValue returns current value.
   */
  public void testLongValue() {
    AtomicDouble at = new AtomicDouble();
    assertEquals(0L, at.longValue());
    for (double x : VALUES) {
      at.set(x);
      assertEquals((long) x, at.longValue());
    }
  }

  /**
   * floatValue returns current value.
   */
  public void testFloatValue() {
    AtomicDouble at = new AtomicDouble();
    assertEquals(0.0f, at.floatValue());
    for (double x : VALUES) {
      at.set(x);
      assertEquals((float) x, at.floatValue());
    }
  }

  /**
   * doubleValue returns current value.
   */
  public void testDoubleValue() {
    AtomicDouble at = new AtomicDouble();
    assertEquals(0.0d, at.doubleValue());
    for (double x : VALUES) {
      at.set(x);
      assertBitEquals(x, at.doubleValue());
    }
  }

  /**
   * compareAndSet treats +0.0 and -0.0 as distinct values
   */
  public void testDistinctZeros() {
    AtomicDouble at = new AtomicDouble(+0.0);
    assertFalse(at.compareAndSet(-0.0, 7.0));
    assertFalse(at.weakCompareAndSet(-0.0, 7.0));
    assertBitEquals(+0.0, at.get());
    assertTrue(at.compareAndSet(+0.0, -0.0));
    assertBitEquals(-0.0, at.get());
    assertFalse(at.compareAndSet(+0.0, 7.0));
    assertFalse(at.weakCompareAndSet(+0.0, 7.0));
    assertBitEquals(-0.0, at.get());
  }
}
