/*
 * 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;

import static com.google.common.truth.Truth.assertThat;
import static java.lang.Math.max;

import org.jspecify.annotations.NullUnmarked;

/** Unit test for {@link AtomicDouble}. */
@NullUnmarked
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 {
    AtomicDouble at = new AtomicDouble(1.0);
    Thread t =
        newStartedThread(
            new CheckedRunnable() {
              @Override
              @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races
              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());
      }
    }
  }

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

  /** getAndAccumulate with max stores max of given value to current, and returns previous value */
  public void testGetAndAccumulateWithMax() {
    for (double x : VALUES) {
      for (double y : VALUES) {
        AtomicDouble a = new AtomicDouble(x);
        double z = a.getAndAccumulate(y, Double::max);
        double expectedMax = max(x, y);
        assertBitEquals(x, z);
        assertBitEquals(expectedMax, a.get());
      }
    }
  }

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

  /** accumulateAndGet with max stores max of given value to current, and returns current value */
  public void testAccumulateAndGetWithMax() {
    for (double x : VALUES) {
      for (double y : VALUES) {
        AtomicDouble a = new AtomicDouble(x);
        double z = a.accumulateAndGet(y, Double::max);
        double expectedMax = max(x, y);
        assertBitEquals(expectedMax, z);
        assertBitEquals(expectedMax, a.get());
      }
    }
  }

  /** getAndUpdate with sum stores sum of given value to current, and returns previous value */
  public void testGetAndUpdateWithSum() {
    for (double x : VALUES) {
      for (double y : VALUES) {
        AtomicDouble a = new AtomicDouble(x);
        double z = a.getAndUpdate(value -> value + y);
        assertBitEquals(x, z);
        assertBitEquals(x + y, a.get());
      }
    }
  }

  /**
   * getAndUpdate with subtract stores subtraction of value from current, and returns previous value
   */
  public void testGetAndUpdateWithSubtract() {
    for (double x : VALUES) {
      for (double y : VALUES) {
        AtomicDouble a = new AtomicDouble(x);
        double z = a.getAndUpdate(value -> value - y);
        assertBitEquals(x, z);
        assertBitEquals(x - y, a.get());
      }
    }
  }

  /** updateAndGet with sum stores sum of given value to current, and returns current value */
  public void testUpdateAndGetWithSum() {
    for (double x : VALUES) {
      for (double y : VALUES) {
        AtomicDouble a = new AtomicDouble(x);
        double z = a.updateAndGet(value -> value + y);
        assertBitEquals(x + y, z);
        assertBitEquals(x + y, a.get());
      }
    }
  }

  /**
   * updateAndGet with subtract stores subtraction of value from current, and returns current value
   */
  public void testUpdateAndGetWithSubtract() {
    for (double x : VALUES) {
      for (double y : VALUES) {
        AtomicDouble a = new AtomicDouble(x);
        double z = a.updateAndGet(value -> value - 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();
    assertThat(at.doubleValue()).isEqualTo(0.0d);
    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());
  }
}
