package org.realityforge.arez.extras;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.realityforge.arez.ArezContext;
import org.realityforge.arez.Observable;
import org.realityforge.arez.ObserverErrorHandler;
import org.realityforge.arez.Procedure;
import org.realityforge.arez.SafeFunction;
import org.realityforge.guiceyloops.shared.ValueUtil;
import org.testng.annotations.Test;
import static org.testng.Assert.*;

public class WatcherTest
{
  @Test
  public void basicOperation()
    throws Exception
  {
    final ArezContext context = new ArezContext();

    final Observable observable = context.createObservable( ValueUtil.randomString() );

    final AtomicBoolean result = new AtomicBoolean();

    final AtomicInteger conditionRun = new AtomicInteger();
    final AtomicInteger effectRun = new AtomicInteger();

    final String name = ValueUtil.randomString();
    final boolean mutation = true;
    final SafeFunction<Boolean> condition = () -> {
      conditionRun.incrementAndGet();
      observable.reportObserved();
      return result.get();
    };
    final Procedure procedure = effectRun::incrementAndGet;

    new Watcher( context, name, mutation, condition, procedure );

    assertEquals( conditionRun.get(), 1 );
    assertEquals( effectRun.get(), 0 );

    context.procedure( ValueUtil.randomString(), true, observable::reportChanged );

    assertEquals( conditionRun.get(), 2 );
    assertEquals( effectRun.get(), 0 );

    result.set( true );

    // Reschedule and run effect
    context.procedure( ValueUtil.randomString(), true, observable::reportChanged );

    assertEquals( conditionRun.get(), 3 );
    assertEquals( effectRun.get(), 1 );

    // Watcher should not be active anymore so this should do nothing
    context.procedure( ValueUtil.randomString(), true, observable::reportChanged );

    assertEquals( conditionRun.get(), 3 );
    assertEquals( effectRun.get(), 1 );
  }

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

    final Observable observable = context.createObservable( ValueUtil.randomString() );

    final AtomicBoolean result = new AtomicBoolean();

    final AtomicInteger conditionRun = new AtomicInteger();
    final AtomicInteger effectRun = new AtomicInteger();

    final String name = ValueUtil.randomString();
    final boolean mutation = true;
    final SafeFunction<Boolean> condition = () -> {
      conditionRun.incrementAndGet();
      observable.reportObserved();
      return result.get();
    };
    final Procedure procedure = effectRun::incrementAndGet;

    final Watcher watcher = new Watcher( context, name, mutation, condition, procedure );

    assertEquals( conditionRun.get(), 1 );
    assertEquals( effectRun.get(), 0 );

    result.set( true );
    watcher.dispose();

    context.procedure( ValueUtil.randomString(), true, observable::reportChanged );

    assertEquals( conditionRun.get(), 1 );
    assertEquals( effectRun.get(), 0 );
  }

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

    final AtomicInteger errorCount = new AtomicInteger();

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

    final Observable observable = context.createObservable( ValueUtil.randomString() );

    final AtomicBoolean result = new AtomicBoolean();

    final AtomicInteger conditionRun = new AtomicInteger();
    final AtomicInteger effectRun = new AtomicInteger();

    final String name = ValueUtil.randomString();
    final boolean mutation = true;
    final SafeFunction<Boolean> condition = () -> {
      conditionRun.incrementAndGet();
      observable.reportObserved();
      observable.reportChanged();
      return result.get();
    };
    final Procedure procedure = effectRun::incrementAndGet;

    new Watcher( context, name, mutation, condition, procedure );

    assertEquals( conditionRun.get(), 1 );
    assertEquals( effectRun.get(), 0 );
    assertEquals( errorCount.get(), 1 );

    result.set( true );

    // Attempt to run condition. Should produce another error and condition evaluation
    context.procedure( ValueUtil.randomString(), true, observable::reportChanged );

    assertEquals( conditionRun.get(), 2 );
    assertEquals( effectRun.get(), 0 );
    assertEquals( errorCount.get(), 2 );
  }

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

    final AtomicInteger errorCount = new AtomicInteger();

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

    final Observable observable = context.createObservable( ValueUtil.randomString() );

    final AtomicInteger effectRun = new AtomicInteger();

    final Procedure effect = () -> {
      effectRun.incrementAndGet();
      observable.reportObserved();
      observable.reportChanged();
    };

    new Watcher( context, ValueUtil.randomString(), false, () -> true, effect );

    assertEquals( effectRun.get(), 1 );
    assertEquals( errorCount.get(), 1 );
  }
}

@@ -5339,11 +5339,825 @@
 1 );%0A  %7D
+%0A%0A  @Test%0A  public void verifyEffectWhenReadWriteTransaction()%0A    throws Exception%0A  %7B%0A    final ArezContext context = new ArezContext();%0A%0A    final AtomicInteger errorCount = new AtomicInteger();%0A%0A    final ObserverErrorHandler handler = ( observer, error, throwable ) -%3E errorCount.incrementAndGet();%0A    context.addObserverErrorHandler( handler );%0A%0A    final Observable observable = context.createObservable( ValueUtil.randomString() );%0A%0A    final AtomicInteger effectRun = new AtomicInteger();%0A%0A    final Procedure effect = () -%3E %7B%0A      effectRun.incrementAndGet();%0A      observable.reportObserved();%0A      observable.reportChanged();%0A    %7D;%0A%0A    new Watcher( context, ValueUtil.randomString(), true, () -%3E true, effect );%0A%0A    assertEquals( effectRun.get(), 1 );%0A    assertEquals( errorCount.get(), 0 );%0A  %7D
 %0A%7D%0A

Verify mutation is ok when transaction is mutating transaciton