package org.realityforge.arez.test;

import java.util.concurrent.atomic.AtomicInteger;
import org.realityforge.arez.AbstractArezTest;
import org.realityforge.arez.ArezContext;
import org.realityforge.arez.Observer;
import org.realityforge.arez.ObserverErrorHandler;
import org.realityforge.arez.ObserverState;
import org.realityforge.arez.Reaction;
import org.realityforge.arez.TransactionMode;
import org.realityforge.guiceyloops.shared.ValueUtil;
import org.testng.annotations.Test;
import static org.testng.Assert.*;

/**
 * This class tests all the public methods of ArezContext
 * that should be visible outside package.
 */
public class ArezContextApiTest
  extends AbstractArezTest
{
  @Test
  public void createObserver_notReaction()
    throws Exception
  {
    final ArezContext context = new ArezContext();

    final String name = ValueUtil.randomString();
    final Observer observer =
      context.createObserver( name, TransactionMode.READ_ONLY, null, false );

    assertEquals( observer.getName(), name );
    assertEquals( observer.getState(), ObserverState.INACTIVE );
  }

  @Test
  public void createReactionObserver()
    throws Exception
  {
    final ArezContext context = new ArezContext();

    final AtomicInteger callCount = new AtomicInteger();

    final String name = ValueUtil.randomString();
    final Reaction reaction = o -> callCount.incrementAndGet();
    final Observer observer =
      context.createObserver( name, TransactionMode.READ_ONLY, reaction, true );

    assertEquals( observer.getName(), name );
    assertEquals( observer.getState(), ObserverState.UP_TO_DATE );
    assertEquals( callCount.get(), 1 );

    observer.dispose();

    assertEquals( observer.getState(), ObserverState.INACTIVE );
  }

  @Test
  public void observerErrorHandler()
    throws Exception
  {
    final ArezContext context = new ArezContext();

    final AtomicInteger callCount = new AtomicInteger();

    final ObserverErrorHandler handler = ( observer, error, throwable ) -> callCount.incrementAndGet();
    context.addObserverErrorHandler( handler );

    final Reaction reaction = o -> {
      throw new RuntimeException();
    };
    // This will run immediately and generate an exception
    context.createObserver( ValueUtil.randomString(), TransactionMode.READ_ONLY, reaction, true );

    assertEquals( callCount.get(), 1 );

    context.removeObserverErrorHandler( handler );

    // This will run immediately and generate an exception
    context.createObserver( ValueUtil.randomString(), TransactionMode.READ_ONLY, reaction, true );

    assertEquals( callCount.get(), 1 );
  }

  @Test
  public void transactionsCanProduceValues()
    throws Exception
  {
    final ArezContext context = new ArezContext();

    assertFalse( context.isTransactionActive() );

    final String expectedValue = ValueUtil.randomString();

    final String v0 =
      context.transaction( ValueUtil.randomString(), TransactionMode.READ_ONLY, null, () -> {
        assertTrue( context.isTransactionActive() );
        return expectedValue;
      } );

    assertFalse( context.isTransactionActive() );

    assertEquals( v0, expectedValue );
  }

  @Test
  public void transactionsCanBeNested()
    throws Exception
  {
    final ArezContext context = new ArezContext();

    assertFalse( context.isTransactionActive() );

    context.transaction( ValueUtil.randomString(), TransactionMode.READ_ONLY, null, () -> {
      assertTrue( context.isTransactionActive() );

      //First nested exception
      context.transaction( ValueUtil.randomString(), TransactionMode.READ_ONLY, null, () -> {
        assertTrue( context.isTransactionActive() );

        //Second nested exception
        context.transaction( ValueUtil.randomString(),
                             TransactionMode.READ_ONLY,
                             null,
                             () -> assertTrue( context.isTransactionActive() ) );

        assertTrue( context.isTransactionActive() );
      } );

      assertTrue( context.isTransactionActive() );
    } );

    assertFalse( context.isTransactionActive() );
  }

  @Test
  public void valueProducingTransactionsCanBeNested()
    throws Exception
  {
    final ArezContext context = new ArezContext();

    assertFalse( context.isTransactionActive() );

    final String expectedValue = ValueUtil.randomString();

    final String v0 =
      context.transaction( ValueUtil.randomString(), TransactionMode.READ_ONLY, null, () -> {
        assertTrue( context.isTransactionActive() );

        //First nested exception
        final String v1 =
          context.transaction( ValueUtil.randomString(), TransactionMode.READ_ONLY, null, () -> {
            assertTrue( context.isTransactionActive() );

            //Second nested exception
            final String v2 =
              context.transaction( ValueUtil.randomString(), TransactionMode.READ_ONLY, null, () -> {
                assertTrue( context.isTransactionActive() );
                return expectedValue;
              } );

            assertTrue( context.isTransactionActive() );

            return v2;
          } );

        assertTrue( context.isTransactionActive() );
        return v1;
      } );

    assertFalse( context.isTransactionActive() );

    assertEquals( v0, expectedValue );
  }
}

@@ -169,16 +169,57 @@
 ontext;%0A
+import org.realityforge.arez.Observable;%0A
 import o
@@ -2646,32 +2646,1135 @@
 et(), 1 );%0A  %7D%0A%0A
+  @Test%0A  public void interactionWithSingleObservable()%0A    throws Exception%0A  %7B%0A    final ArezContext context = new ArezContext();%0A%0A    final Observable observable = context.createObservable( ValueUtil.randomString() );%0A%0A    final AtomicInteger reactionCount = new AtomicInteger();%0A%0A    final Observer observer =%0A      context.createObserver( ValueUtil.randomString(),%0A                              TransactionMode.READ_ONLY,%0A                              o -%3E %7B%0A                                observable.reportObserved();%0A                                reactionCount.incrementAndGet();%0A                              %7D,%0A                              true );%0A%0A    assertEquals( reactionCount.get(), 1 );%0A    assertEquals( observer.getState(), ObserverState.UP_TO_DATE );%0A%0A    // Run an %22action%22%0A    context.transaction( ValueUtil.randomString(),%0A                         TransactionMode.READ_WRITE,%0A                         null,%0A                         observable::reportChanged );%0A%0A    assertEquals( reactionCount.get(), 2 );%0A    assertEquals( observer.getState(), ObserverState.UP_TO_DATE );%0A  %7D%0A%0A
   @Test%0A  public

Add a simple test where a simple non-calculated observer is interacted with