package arez;

import java.lang.reflect.Field;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.realityforge.braincheck.BrainCheckTestUtil;
import org.realityforge.guiceyloops.shared.ValueUtil;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;

public abstract class AbstractArezTest
{
  @BeforeMethod
  protected void beforeTest()
    throws Exception
  {
    BrainCheckTestUtil.resetConfig( false );
    ArezTestUtil.resetConfig( false );
    ArezTestUtil.enableZones();
    getProxyLogger().setLogger( new TestLogger() );
  }

  @AfterMethod
  protected void afterTest()
    throws Exception
  {
    BrainCheckTestUtil.resetConfig( true );
    ArezTestUtil.resetConfig( true );
  }

  @Nonnull
  final TestLogger getTestLogger()
  {
    return (TestLogger) getProxyLogger().getLogger();
  }

  @Nonnull
  private ArezLogger.ProxyLogger getProxyLogger()
  {
    return (ArezLogger.ProxyLogger) ArezLogger.getLogger();
  }

  @Nonnull
  private Field getField( @Nonnull final Class<?> type, @Nonnull final String fieldName )
    throws NoSuchFieldException
  {
    Class clazz = type;
    while ( null != clazz && Object.class != clazz )
    {
      try
      {
        final Field field = clazz.getDeclaredField( fieldName );
        field.setAccessible( true );
        return field;
      }
      catch ( final Throwable t )
      {
        clazz = clazz.getSuperclass();
      }
    }

    Assert.fail();
    throw new IllegalStateException();
  }

  @SuppressWarnings( "SameParameterValue" )
  final void setField( @Nonnull final Object object, @Nonnull final String fieldName, @Nullable final Object value )
    throws NoSuchFieldException, IllegalAccessException
  {
    getField( object.getClass(), fieldName ).set( object, value );
  }

  /**
   * Typically called to stop observer from being deactivate or stop invariant checks failing.
   */
  @SuppressWarnings( "UnusedReturnValue" )
  @Nonnull
  final Observer ensureDerivationHasObserver( @Nonnull final Observer observer )
  {
    final Observer randomObserver = newReadOnlyObserver( observer.getContext() );
    randomObserver.setState( ObserverState.UP_TO_DATE );
    observer.getDerivedValue().addObserver( randomObserver );
    randomObserver.getDependencies().add( observer.getDerivedValue() );
    return randomObserver;
  }

  @Nonnull
  final Observer newReadWriteObserver( @Nonnull final ArezContext context )
  {
    return new Observer( context,
                         null,
                         ValueUtil.randomString(),
                         null,
                         TransactionMode.READ_WRITE,
                         new TestReaction(),
                         false );
  }

  @Nonnull
  final Observer newDerivation( @Nonnull final ArezContext context )
  {
    return new ComputedValue<>( context, null, ValueUtil.randomString(), () -> "", Objects::equals ).getObserver();
  }

  @Nonnull
  final Observer newObserver( @Nonnull final ArezContext context )
  {
    return newReadOnlyObserver( context );
  }

  @Nonnull
  final Observer newReadOnlyObserver( @Nonnull final ArezContext context )
  {
    return new Observer( context,
                         null,
                         ValueUtil.randomString(),
                         null,
                         TransactionMode.READ_ONLY,
                         new TestReaction(),
                         false );
  }

  @Nonnull
  final Observable<?> newObservable( final ArezContext context )
  {
    return new Observable<>( context, null, ValueUtil.randomString(), null, null, null );
  }

  final void setCurrentTransaction( @Nonnull final ArezContext context )
  {
    setCurrentTransaction( newReadOnlyObserver( context ) );
  }

  final void setCurrentTransaction( @Nonnull final Observer observer )
  {
    final ArezContext context = observer.getContext();
    Transaction.setTransaction( new Transaction( context,
                                                 null,
                                                 ValueUtil.randomString(),
                                                 observer.getMode(),
                                                 observer ) );
  }
}

@@ -3111,32 +3111,141 @@
 context );%0A  %7D%0A%0A
+  @Nonnull%0A  final Observer newReadOnlyObserver()%0A  %7B%0A    return newReadOnlyObserver( Arez.context() );%0A  %7D%0A%0A
   @Nonnull%0A  fin
@@ -3306,32 +3306,32 @@
 ntext context )%0A
-
   %7B%0A    return n
@@ -3593,32 +3593,134 @@
   false );%0A  %7D%0A%0A
+  @Nonnull%0A  final Observable%3C?%3E newObservable()%0A  %7B%0A    return newObservable( Arez.context() );%0A  %7D%0A%0A
   @Nonnull%0A  fin
@@ -3951,24 +3951,24 @@
 ntext )%0A  %7B%0A
-
     setCurre
@@ -4013,32 +4013,279 @@
 ntext ) );%0A  %7D%0A%0A
+  final void setupReadWriteTransaction()%0A  %7B%0A    setupReadWriteTransaction( Arez.context() );%0A  %7D%0A%0A  final void setupReadWriteTransaction( @Nonnull final ArezContext context )%0A  %7B%0A    setCurrentTransaction( newReadWriteObserver( context ) );%0A  %7D%0A%0A
   final void set

Add some helper methods that create elements using the current context