package org.realityforge.arez.api2;

import java.util.concurrent.Callable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jetbrains.annotations.TestOnly;

/**
 * The ArezContext defines the top level container of interconnected observables and observers.
 * The context also provides the mechanism for creating transactions to read and write state
 * within the system.
 */
public final class ArezContext
{
  /**
   * All changes in a context must occur within the scope of a transaction.
   * This references the current transaction.
   */
  @Nullable
  private Transaction _transaction;
  /**
   * Id of next node to be created.
   *
   * This needs to start at 1 as {@link Observable#NOT_IN_CURRENT_TRACKING} is used
   * to optimize dependency tracking in transactions.
   */
  private int _nextNodeId = 1;
  /**
   * Reaction Scheduler.
   * Currently hard-coded, in the future potentially configurable.
   */
  private final ReactionScheduler _scheduler = new ReactionScheduler( this );

  /**
   * Pass the supplied reaction to the scheduler.
   * The reaction should NOT be already pending execution.
   *
   * @param reaction the reaction to schedule.
   */
  final void scheduleReaction( @Nonnull final Reaction reaction )
  {
    _scheduler.scheduleReaction( reaction );
  }

  /**
   * Run all pending reactions.
   */
  final void runPendingReactions()
  {
    _scheduler.runPendingReactions();
  }

  /**
   * Create a new transaction.
   *
   * @param name    the name of the transaction. Should be non-null if {@link ArezConfig#enableNames()} is true, false otherwise.
   * @param mode    the transaciton mode.
   * @param tracker the observer that is tracking transaction if any.
   * @return the new transaction.
   */
  private Transaction beginTransaction( @Nullable final String name,
                                        @Nonnull final TransactionMode mode,
                                        @Nullable final Observer tracker )
  {
    _transaction = new Transaction( this, _transaction, name, mode, tracker );
    _transaction.begin();
    return _transaction;
  }

  /**
   * Commit the supplied transaction.
   *
   * This method verifies that the transaction active is the supplied transaction before committing
   * the transaction and restoring the prior transaction if any.
   *
   * @param transaction the transaction.
   */
  private void commitTransaction( @Nonnull final Transaction transaction )
  {
    Guards.invariant( () -> null != _transaction,
                      () -> String.format( "Attempting to commit transaction named '%s' but no transaction is active.",
                                           transaction.getName() ) );
    assert null != _transaction;
    Guards.invariant( () -> _transaction == transaction,
                      () -> String.format(
                        "Attempting to commit transaction named '%s' but this does not match existing transaction named '%s'.",
                        transaction.getName(),
                        _transaction.getName() ) );
    _transaction.commit();
    _transaction = _transaction.getPrevious();
  }

  /**
   * Execute the supplied action in a transaction.
   * The transaction is tracking if tracker is supplied and is named with specified name.
   *
   * @param <T>     the type of return value.
   * @param name    the name of the transaction. Should be non-null if {@link ArezConfig#enableNames()} is true, false otherwise.
   * @param mode    the transaciton mode.
   * @param tracker the observer that is tracking transaction if any.
   * @param action  the action to execute.
   * @return the value returned from the action.
   * @throws Exception if the action throws an an exception.
   */
  public <T> T transaction( @Nullable final String name,
                            @Nonnull final TransactionMode mode,
                            @Nullable final Observer tracker,
                            @Nonnull final Callable<T> action )
    throws Exception
  {
    final Transaction transaction = beginTransaction( name, mode, tracker );
    try
    {
      return action.call();
    }
    finally
    {
      commitTransaction( transaction );
    }
  }

  /**
   * Execute the supplied action in a transaction.
   * The transaction is tracking if tracker is supplied and is named with specified name.
   *
   * @param name    the name of the transaction. Should be non-null if {@link ArezConfig#enableNames()} is true, false otherwise.
   * @param mode    the transaciton mode.
   * @param tracker the observer that is tracking transaction if any.
   * @param action  the action to execute.
   * @throws Exception if the action throws an an exception.
   */
  public void transaction( @Nullable final String name,
                           @Nonnull final TransactionMode mode,
                           @Nullable final Observer tracker,
                           @Nonnull final Action action )
    throws Exception
  {
    final Transaction transaction = beginTransaction( name, mode, tracker );
    try
    {
      action.call();
    }
    finally
    {
      commitTransaction( transaction );
    }
  }

  /**
   * Return true if there is a transaction in progress.
   *
   * @return true if there is a transaction in progress.
   */
  public boolean isTransactionActive()
  {
    return null != _transaction;
  }

  /**
   * Return the current transaction.
   * This method should not be invoked unless a transaction active and will throw an
   * exception if invariant checks are enabled.
   *
   * @return the current transaction.
   */
  @Nonnull
  Transaction getTransaction()
  {
    Guards.invariant( this::isTransactionActive,
                      () -> "Attempting to get current transaction but no transaction is active." );
    assert null != _transaction;
    return _transaction;
  }

  /**
   * Return next node id and increment internal counter.
   * The id is a monotonically increasing number starting at 1.
   *
   * @return the next node id.
   */
  int nextNodeId()
  {
    return _nextNodeId++;
  }

  @TestOnly
  final int currentNextNodeId()
  {
    return _nextNodeId;
  }

  @TestOnly
  final void setTransaction( @Nullable final Transaction transaction )
  {
    _transaction = transaction;
  }
}

@@ -1189,30 +1189,24 @@
 le.%0A   */%0A  
-final 
 void schedul
@@ -1351,22 +1351,16 @@
    */%0A  
-final 
 void run
@@ -6086,22 +6086,16 @@
 tOnly%0A  
-final 
 int curr
@@ -6157,22 +6157,16 @@
 tOnly%0A  
-final 
 void set

Remove unnecessary finals as class is final